summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth/OAuth/ChannelElements
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-06-25 19:51:02 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2009-06-25 19:51:02 -0700
commit3b2fe4819cf449d1ab88178d055239c64b3cfd5e (patch)
treee191391cc71af22a854a06fee0079dbb74405752 /src/DotNetOpenAuth/OAuth/ChannelElements
parent97e3fc44a6911289baf3435febc0b003e56ad4e8 (diff)
parentf3ce247dfaa965011c7f8417d00e0fa3dfad4a35 (diff)
downloadDotNetOpenAuth-3b2fe4819cf449d1ab88178d055239c64b3cfd5e.zip
DotNetOpenAuth-3b2fe4819cf449d1ab88178d055239c64b3cfd5e.tar.gz
DotNetOpenAuth-3b2fe4819cf449d1ab88178d055239c64b3cfd5e.tar.bz2
Merge branch 'master' into contracts
Conflicts: src/DotNetOpenAuth.vsmdi src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs src/DotNetOpenAuth/DotNetOpenAuth.csproj src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs src/DotNetOpenAuth/OAuth/ConsumerBase.cs src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs src/DotNetOpenAuth/OAuth/ServiceProvider.cs src/DotNetOpenAuth/OpenId/Protocol.cs src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs src/DotNetOpenAuth/OpenId/Provider/Request.cs src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs
Diffstat (limited to 'src/DotNetOpenAuth/OAuth/ChannelElements')
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs23
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs59
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs45
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs52
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs37
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs39
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs63
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs89
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs60
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs58
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs190
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs78
13 files changed, 716 insertions, 83 deletions
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs
deleted file mode 100644
index 7e6ae54..0000000
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-//-----------------------------------------------------------------------
-// <copyright file="IConsumerCertificateProvider.cs" company="Andrew Arnott">
-// Copyright (c) Andrew Arnott. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOpenAuth.OAuth.ChannelElements {
- using System.Security.Cryptography.X509Certificates;
-
- /// <summary>
- /// A provider that hosts can implement to hook up their RSA-SHA1 binding elements
- /// to their list of known Consumers' certificates.
- /// </summary>
- public interface IConsumerCertificateProvider {
- /// <summary>
- /// Gets the certificate that can be used to verify the signature of an incoming
- /// message from a Consumer.
- /// </summary>
- /// <param name="consumerMessage">The incoming message from some Consumer.</param>
- /// <returns>The public key from the Consumer's X.509 Certificate, if one can be found; otherwise <c>null</c>.</returns>
- X509Certificate2 GetCertificate(ITamperResistantOAuthMessage consumerMessage);
- }
-}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs
new file mode 100644
index 0000000..db505d5
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------
+// <copyright file="IConsumerDescription.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Security.Cryptography.X509Certificates;
+
+ /// <summary>
+ /// A description of a consumer from a Service Provider's point of view.
+ /// </summary>
+ public interface IConsumerDescription {
+ /// <summary>
+ /// Gets the Consumer key.
+ /// </summary>
+ string Key { get; }
+
+ /// <summary>
+ /// Gets the consumer secret.
+ /// </summary>
+ string Secret { get; }
+
+ /// <summary>
+ /// Gets the certificate that can be used to verify the signature of an incoming
+ /// message from a Consumer.
+ /// </summary>
+ /// <returns>The public key from the Consumer's X.509 Certificate, if one can be found; otherwise <c>null</c>.</returns>
+ /// <remarks>
+ /// This property must be implemented only if the RSA-SHA1 algorithm is supported by the Service Provider.
+ /// </remarks>
+ X509Certificate2 Certificate { get; }
+
+ /// <summary>
+ /// Gets the callback URI that this consumer has pre-registered with the service provider, if any.
+ /// </summary>
+ /// <value>A URI that user authorization responses should be directed to; or <c>null</c> if no preregistered callback was arranged.</value>
+ Uri Callback { get; }
+
+ /// <summary>
+ /// Gets the verification code format that is most appropriate for this consumer
+ /// when a callback URI is not available.
+ /// </summary>
+ /// <value>A set of characters that can be easily keyed in by the user given the Consumer's
+ /// application type and form factor.</value>
+ /// <remarks>
+ /// The value <see cref="OAuth.VerificationCodeFormat.IncludedInCallback"/> should NEVER be returned
+ /// since this property is only used in no callback scenarios anyway.
+ /// </remarks>
+ VerificationCodeFormat VerificationCodeFormat { get; }
+
+ /// <summary>
+ /// Gets the length of the verification code to issue for this Consumer.
+ /// </summary>
+ /// <value>A positive number, generally at least 4.</value>
+ int VerificationCodeLength { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs
new file mode 100644
index 0000000..329ac4d
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="IServiceProviderAccessToken.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// A description of an access token and its metadata as required by a Service Provider.
+ /// </summary>
+ public interface IServiceProviderAccessToken {
+ /// <summary>
+ /// Gets the token itself.
+ /// </summary>
+ string Token { get; }
+
+ /// <summary>
+ /// Gets the expiration date (local time) for the access token.
+ /// </summary>
+ /// <value>The expiration date, or <c>null</c> if there is no expiration date.</value>
+ DateTime? ExpirationDate { get; }
+
+ /// <summary>
+ /// Gets the username of the principal that will be impersonated by this access token.
+ /// </summary>
+ /// <value>
+ /// The name of the user who authorized the OAuth request token originally.
+ /// </value>
+ string Username { get; }
+
+ /// <summary>
+ /// Gets the roles that the OAuth principal should belong to.
+ /// </summary>
+ /// <value>
+ /// The roles that the user belongs to, or a subset of these according to the rights
+ /// granted when the user authorized the request token.
+ /// </value>
+ string[] Roles { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs
new file mode 100644
index 0000000..6dfa416
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------
+// <copyright file="IServiceProviderRequestToken.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <summary>
+ /// A description of a request token and its metadata as required by a Service Provider
+ /// </summary>
+ public interface IServiceProviderRequestToken {
+ /// <summary>
+ /// Gets the token itself.
+ /// </summary>
+ string Token { get; }
+
+ /// <summary>
+ /// Gets the consumer key that requested this token.
+ /// </summary>
+ string ConsumerKey { get; }
+
+ /// <summary>
+ /// Gets the (local) date that this request token was first created on.
+ /// </summary>
+ DateTime CreatedOn { get; }
+
+ /// <summary>
+ /// Gets or sets the callback associated specifically with this token, if any.
+ /// </summary>
+ /// <value>The callback URI; or <c>null</c> if no callback was specifically assigned to this token.</value>
+ Uri Callback { get; set; }
+
+ /// <summary>
+ /// Gets or sets the verifier that the consumer must include in the <see cref="AuthorizedTokenRequest"/>
+ /// message to exchange this request token for an access token.
+ /// </summary>
+ /// <value>The verifier code, or <c>null</c> if none has been assigned (yet).</value>
+ string VerificationCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the version of the Consumer that requested this token.
+ /// </summary>
+ /// <remarks>
+ /// This property is used to determine whether a <see cref="VerificationCode"/> must be
+ /// generated when the user authorizes the Consumer or not.
+ /// </remarks>
+ Version ConsumerVersion { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs
index e1c1e3f..02ebffb 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs
@@ -16,12 +16,39 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </summary>
public interface IServiceProviderTokenManager : ITokenManager {
/// <summary>
- /// Gets the Consumer Secret for a given a Consumer Key.
+ /// Gets the Consumer description for a given a Consumer Key.
/// </summary>
/// <param name="consumerKey">The Consumer Key.</param>
- /// <returns>The Consumer Secret.</returns>
- /// <exception cref="ArgumentException">Thrown if the consumer key cannot be found.</exception>
- /// <exception cref="InvalidOperationException">May be thrown if called when the signature algorithm does not require a consumer secret, such as when RSA-SHA1 is used.</exception>
- string GetConsumerSecret(string consumerKey);
+ /// <returns>A description of the consumer. Never null.</returns>
+ /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception>
+ IConsumerDescription GetConsumer(string consumerKey);
+
+ /// <summary>
+ /// Gets details on the named request token.
+ /// </summary>
+ /// <param name="token">The request token.</param>
+ /// <returns>A description of the token. Never null.</returns>
+ /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception>
+ /// <remarks>
+ /// It is acceptable for implementations to find the token, see that it has expired,
+ /// delete it from the database and then throw <see cref="KeyNotFoundException"/>,
+ /// or alternatively it can return the expired token anyway and the OAuth channel will
+ /// log and throw the appropriate error.
+ /// </remarks>
+ IServiceProviderRequestToken GetRequestToken(string token);
+
+ /// <summary>
+ /// Gets details on the named access token.
+ /// </summary>
+ /// <param name="token">The access token.</param>
+ /// <returns>A description of the token. Never null.</returns>
+ /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception>
+ /// <remarks>
+ /// It is acceptable for implementations to find the token, see that it has expired,
+ /// delete it from the database and then throw <see cref="KeyNotFoundException"/>,
+ /// or alternatively it can return the expired token anyway and the OAuth channel will
+ /// log and throw the appropriate error.
+ /// </remarks>
+ IServiceProviderAccessToken GetAccessToken(string token);
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index cd34f6f..a36287f 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// <see cref="OAuthConsumerMessageFactory"/> or <see cref="OAuthServiceProviderMessageFactory"/>.
/// </param>
internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, IMessageFactory messageTypeProvider)
- : base(messageTypeProvider, new OAuthHttpMethodBindingElement(), signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) {
+ : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager)) {
Contract.Requires<ArgumentNullException>(tokenManager != null);
this.TokenManager = tokenManager;
@@ -120,7 +120,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
string authorization = request.Headers[HttpRequestHeader.Authorization];
if (authorization != null) {
string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
- string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " ";
+ string oauthPrefix = Protocol.AuthorizationHeaderScheme + " ";
// The Authorization header may have multiple uses, and OAuth may be just one of them.
// Go through each one looking for an OAuth one.
@@ -140,8 +140,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
}
// Scrape the entity
- foreach (string key in request.Form) {
- fields.Add(key, request.Form[key]);
+ if (string.Equals(request.Headers[HttpRequestHeader.ContentType], HttpFormUrlEncoded, StringComparison.Ordinal)) {
+ foreach (string key in request.Form) {
+ fields.Add(key, request.Form[key]);
+ }
}
// Scrape the query string
@@ -155,7 +157,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
// Add receiving HTTP transport information required for signature generation.
var signedMessage = message as ITamperResistantOAuthMessage;
if (signedMessage != null) {
- signedMessage.Recipient = request.Url;
+ signedMessage.Recipient = request.UrlBeforeRewriting;
signedMessage.HttpMethod = request.HttpMethod;
}
@@ -230,6 +232,29 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
}
/// <summary>
+ /// Initializes the binding elements for the OAuth channel.
+ /// </summary>
+ /// <param name="signingBindingElement">The signing binding element.</param>
+ /// <param name="store">The nonce store.</param>
+ /// <param name="tokenManager">The token manager.</param>
+ /// <returns>An array of binding elements used to initialize the channel.</returns>
+ private static IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager) {
+ var bindingElements = new List<IChannelBindingElement> {
+ new OAuthHttpMethodBindingElement(),
+ signingBindingElement,
+ new StandardExpirationBindingElement(),
+ new StandardReplayProtectionBindingElement(store),
+ };
+
+ var spTokenManager = tokenManager as IServiceProviderTokenManager;
+ if (spTokenManager != null) {
+ bindingElements.Insert(0, new TokenHandlingBindingElement(spTokenManager));
+ }
+
+ return bindingElements.ToArray();
+ }
+
+ /// <summary>
/// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1.
/// </summary>
/// <param name="source">The dictionary with names and values to encode.</param>
@@ -295,7 +320,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
httpRequest.Method = GetHttpMethod(requestMessage);
StringBuilder authorization = new StringBuilder();
- authorization.Append(protocol.AuthorizationHeaderScheme);
+ authorization.Append(Protocol.AuthorizationHeaderScheme);
authorization.Append(" ");
foreach (var pair in fields) {
string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key);
@@ -354,7 +379,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
ErrorUtilities.VerifyInternal(consumerKey == consumerTokenManager.ConsumerKey, "The token manager consumer key and the consumer key set earlier do not match!");
return consumerTokenManager.ConsumerSecret;
} else {
- return ((IServiceProviderTokenManager)this.TokenManager).GetConsumerSecret(consumerKey);
+ return ((IServiceProviderTokenManager)this.TokenManager).GetConsumer(consumerKey).Secret;
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs
index 5d76afc..327b923 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs
@@ -42,7 +42,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
MessageBase message = null;
if (fields.ContainsKey("oauth_token")) {
- message = new UserAuthorizationResponse(recipient.Location);
+ Protocol protocol = fields.ContainsKey("oauth_verifier") ? Protocol.V10a : Protocol.V10;
+ message = new UserAuthorizationResponse(recipient.Location, protocol.Version);
}
if (message != null) {
@@ -87,7 +88,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
var unauthorizedTokenRequest = request as UnauthorizedTokenRequest;
var authorizedTokenRequest = request as AuthorizedTokenRequest;
if (unauthorizedTokenRequest != null) {
- message = new UnauthorizedTokenResponse(unauthorizedTokenRequest);
+ Protocol protocol = fields.ContainsKey("oauth_callback_confirmed") ? Protocol.V10a : Protocol.V10;
+ message = new UnauthorizedTokenResponse(unauthorizedTokenRequest, protocol.Version);
} else if (authorizedTokenRequest != null) {
message = new AuthorizedTokenResponse(authorizedTokenRequest);
} else {
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs
new file mode 100644
index 0000000..0de2c15
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthIdentity.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Runtime.InteropServices;
+ using System.Security.Principal;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Represents an OAuth consumer that is impersonating a known user on the system.
+ /// </summary>
+ [Serializable]
+ [ComVisible(true)]
+ public class OAuthIdentity : IIdentity {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthIdentity"/> class.
+ /// </summary>
+ /// <param name="username">The username.</param>
+ internal OAuthIdentity(string username) {
+ Contract.Requires(!String.IsNullOrEmpty(username));
+ ErrorUtilities.VerifyNonZeroLength(username, "username");
+ this.Name = username;
+ }
+
+ #region IIdentity Members
+
+ /// <summary>
+ /// Gets the type of authentication used.
+ /// </summary>
+ /// <value>The constant "OAuth"</value>
+ /// <returns>
+ /// The type of authentication used to identify the user.
+ /// </returns>
+ public string AuthenticationType {
+ get { return "OAuth"; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the user has been authenticated.
+ /// </summary>
+ /// <value>The value <c>true</c></value>
+ /// <returns>true if the user was authenticated; otherwise, false.
+ /// </returns>
+ public bool IsAuthenticated {
+ get { return true; }
+ }
+
+ /// <summary>
+ /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization.
+ /// </summary>
+ /// <returns>
+ /// The name of the user on whose behalf the code is running.
+ /// </returns>
+ public string Name { get; private set; }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs
new file mode 100644
index 0000000..689c388
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Runtime.InteropServices;
+ using System.Security.Principal;
+
+ /// <summary>
+ /// Represents an OAuth consumer that is impersonating a known user on the system.
+ /// </summary>
+ [Serializable]
+ [ComVisible(true)]
+ public class OAuthPrincipal : IPrincipal {
+ /// <summary>
+ /// The roles this user belongs to.
+ /// </summary>
+ private ICollection<string> roles;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class.
+ /// </summary>
+ /// <param name="token">The access token.</param>
+ internal OAuthPrincipal(IServiceProviderAccessToken token)
+ : this(token.Username, token.Roles) {
+ Contract.Requires(token != null);
+
+ this.AccessToken = token.Token;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class.
+ /// </summary>
+ /// <param name="identity">The identity.</param>
+ /// <param name="roles">The roles this user belongs to.</param>
+ internal OAuthPrincipal(OAuthIdentity identity, string[] roles) {
+ this.Identity = identity;
+ this.roles = roles;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class.
+ /// </summary>
+ /// <param name="username">The username.</param>
+ /// <param name="roles">The roles this user belongs to.</param>
+ internal OAuthPrincipal(string username, string[] roles)
+ : this(new OAuthIdentity(username), roles) {
+ }
+
+ /// <summary>
+ /// Gets the access token used to create this principal.
+ /// </summary>
+ /// <value>A non-empty string.</value>
+ public string AccessToken { get; private set; }
+
+ #region IPrincipal Members
+
+ /// <summary>
+ /// Gets the identity of the current principal.
+ /// </summary>
+ /// <value></value>
+ /// <returns>
+ /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal.
+ /// </returns>
+ public IIdentity Identity { get; private set; }
+
+ /// <summary>
+ /// Determines whether the current principal belongs to the specified role.
+ /// </summary>
+ /// <param name="role">The name of the role for which to check membership.</param>
+ /// <returns>
+ /// true if the current principal is a member of the specified role; otherwise, false.
+ /// </returns>
+ /// <remarks>
+ /// The role membership check uses <see cref="StringComparer.OrdinalIgnoreCase"/>.
+ /// </remarks>
+ public bool IsInRole(string role) {
+ return this.roles.Contains(role, StringComparer.OrdinalIgnoreCase);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
index 767a0b9..429615a 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
@@ -52,29 +52,51 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </remarks>
public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) {
MessageBase message = null;
+ Protocol protocol = Protocol.V10; // default to assuming the less-secure 1.0 instead of 1.0a until we prove otherwise.
+ string token;
+ fields.TryGetValue("oauth_token", out token);
- if (fields.ContainsKey("oauth_consumer_key") &&
- !fields.ContainsKey("oauth_token")) {
- message = new UnauthorizedTokenRequest(recipient);
- } else if (fields.ContainsKey("oauth_consumer_key") &&
- fields.ContainsKey("oauth_token")) {
- // Discern between RequestAccessToken and AccessProtectedResources,
- // which have all the same parameters, by figuring out what type of token
- // is in the token parameter.
- bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(fields["oauth_token"]) == TokenType.AccessToken;
+ try {
+ if (fields.ContainsKey("oauth_consumer_key") && !fields.ContainsKey("oauth_token")) {
+ protocol = fields.ContainsKey("oauth_callback") ? Protocol.V10a : Protocol.V10;
+ message = new UnauthorizedTokenRequest(recipient, protocol.Version);
+ } else if (fields.ContainsKey("oauth_consumer_key") && fields.ContainsKey("oauth_token")) {
+ // Discern between RequestAccessToken and AccessProtectedResources,
+ // which have all the same parameters, by figuring out what type of token
+ // is in the token parameter.
+ bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(token) == TokenType.AccessToken;
- message = tokenTypeIsAccessToken ? (MessageBase)new AccessProtectedResourceRequest(recipient) :
- new AuthorizedTokenRequest(recipient);
- } else {
- // fail over to the message with no required fields at all.
- message = new UserAuthorizationRequest(recipient);
- }
+ if (tokenTypeIsAccessToken) {
+ message = (MessageBase)new AccessProtectedResourceRequest(recipient, protocol.Version);
+ } else {
+ // Discern between 1.0 and 1.0a requests by checking on the consumer version we stored
+ // when the consumer first requested an unauthorized token.
+ protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion);
+ message = new AuthorizedTokenRequest(recipient, protocol.Version);
+ }
+ } else {
+ // fail over to the message with no required fields at all.
+ if (token != null) {
+ protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion);
+ }
- if (message != null) {
- message.SetAsIncoming();
- }
+ // If a callback parameter is included, that suggests either the consumer
+ // is following OAuth 1.0 instead of 1.0a, or that a hijacker is trying
+ // to attack. Either way, if the consumer started out as a 1.0a, keep it
+ // that way, and we'll just ignore the oauth_callback included in this message
+ // by virtue of the UserAuthorizationRequest message not including it in its
+ // 1.0a payload.
+ message = new UserAuthorizationRequest(recipient, protocol.Version);
+ }
- return message;
+ if (message != null) {
+ message.SetAsIncoming();
+ }
+
+ return message;
+ } catch (KeyNotFoundException ex) {
+ throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound);
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs
index 779f2c5..4f8b5e5 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth.ChannelElements {
using System;
+ using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@@ -16,15 +17,24 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </summary>
public class RsaSha1SigningBindingElement : SigningBindingElementBase {
/// <summary>
+ /// The name of the hash algorithm to use.
+ /// </summary>
+ private const string HashAlgorithmName = "RSA-SHA1";
+
+ /// <summary>
+ /// The token manager for the service provider.
+ /// </summary>
+ private IServiceProviderTokenManager tokenManager;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class
/// for use by Consumers.
/// </summary>
/// <param name="signingCertificate">The certificate used to sign outgoing messages.</param>
public RsaSha1SigningBindingElement(X509Certificate2 signingCertificate)
- : this() {
- if (signingCertificate == null) {
- throw new ArgumentNullException("signingCertificate");
- }
+ : base(HashAlgorithmName) {
+ Contract.Requires(signingCertificate != null);
+ ErrorUtilities.VerifyArgumentNotNull(signingCertificate, "signingCertificate");
this.SigningCertificate = signingCertificate;
}
@@ -33,21 +43,21 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class
/// for use by Service Providers.
/// </summary>
- public RsaSha1SigningBindingElement()
- : base("RSA-SHA1") {
+ /// <param name="tokenManager">The token manager.</param>
+ public RsaSha1SigningBindingElement(IServiceProviderTokenManager tokenManager)
+ : base(HashAlgorithmName) {
+ Contract.Requires(tokenManager != null);
+ ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager");
+
+ this.tokenManager = tokenManager;
}
/// <summary>
- /// Gets or sets the certificate used to sign outgoing messages.
+ /// Gets or sets the certificate used to sign outgoing messages. Used only by Consumers.
/// </summary>
public X509Certificate2 SigningCertificate { get; set; }
/// <summary>
- /// Gets or sets the consumer certificate provider.
- /// </summary>
- public IConsumerCertificateProvider ConsumerCertificateProvider { get; set; }
-
- /// <summary>
/// Calculates a signature for a given message.
/// </summary>
/// <param name="message">The message to sign.</param>
@@ -56,13 +66,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// This method signs the message per OAuth 1.0 section 9.3.
/// </remarks>
protected override string GetSignature(ITamperResistantOAuthMessage message) {
- if (message == null) {
- throw new ArgumentNullException("message");
- }
-
- if (this.SigningCertificate == null) {
- throw new InvalidOperationException(OAuthStrings.X509CertificateNotProvidedForSigning);
- }
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+ ErrorUtilities.VerifyOperation(this.SigningCertificate != null, OAuthStrings.X509CertificateNotProvidedForSigning);
string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message));
byte[] data = Encoding.ASCII.GetBytes(signatureBaseString);
@@ -80,16 +85,14 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>.
/// </returns>
protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) {
- if (this.ConsumerCertificateProvider == null) {
- throw new InvalidOperationException(OAuthStrings.ConsumerCertificateProviderNotAvailable);
- }
+ ErrorUtilities.VerifyInternal(this.tokenManager != null, "No token manager available for fetching Consumer public certificates.");
string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message));
byte[] data = Encoding.ASCII.GetBytes(signatureBaseString);
byte[] carriedSignature = Convert.FromBase64String(message.Signature);
- X509Certificate2 cert = this.ConsumerCertificateProvider.GetCertificate(message);
+ X509Certificate2 cert = this.tokenManager.GetConsumer(message.ConsumerKey).Certificate;
if (cert == null) {
Logger.Signatures.WarnFormat("Incoming message from consumer '{0}' could not be matched with an appropriate X.509 certificate for signature verification.", message.ConsumerKey);
return false;
@@ -105,10 +108,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </summary>
/// <returns>A new instance of the binding element.</returns>
protected override ITamperProtectionChannelBindingElement Clone() {
- return new RsaSha1SigningBindingElement() {
- ConsumerCertificateProvider = this.ConsumerCertificateProvider,
- SigningCertificate = this.SigningCertificate,
- };
+ if (this.tokenManager != null) {
+ return new RsaSha1SigningBindingElement(this.tokenManager);
+ } else {
+ return new RsaSha1SigningBindingElement(this.SigningCertificate);
+ }
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs
new file mode 100644
index 0000000..ce7bb98
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenHandlingBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <summary>
+ /// A binding element for Service Providers to manage the
+ /// callbacks and verification codes on applicable messages.
+ /// </summary>
+ internal class TokenHandlingBindingElement : IChannelBindingElement {
+ /// <summary>
+ /// The token manager offered by the service provider.
+ /// </summary>
+ private IServiceProviderTokenManager tokenManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TokenHandlingBindingElement"/> class.
+ /// </summary>
+ /// <param name="tokenManager">The token manager.</param>
+ internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager) {
+ Contract.Requires(tokenManager != null);
+ ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager");
+
+ this.tokenManager = tokenManager;
+ }
+
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+
+ var userAuthResponse = message as UserAuthorizationResponse;
+ if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) {
+ this.tokenManager.GetRequestToken(userAuthResponse.RequestToken).VerificationCode = userAuthResponse.VerificationCode;
+ return MessageProtections.None;
+ }
+
+ // Hook to store the token and secret on its way down to the Consumer.
+ var grantRequestTokenResponse = message as UnauthorizedTokenResponse;
+ if (grantRequestTokenResponse != null) {
+ this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse);
+ this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).ConsumerVersion = grantRequestTokenResponse.Version;
+ if (grantRequestTokenResponse.RequestMessage.Callback != null) {
+ this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).Callback = grantRequestTokenResponse.RequestMessage.Callback;
+ }
+
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+
+ var authorizedTokenRequest = message as AuthorizedTokenRequest;
+ if (authorizedTokenRequest != null) {
+ if (authorizedTokenRequest.Version >= Protocol.V10a.Version) {
+ string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode;
+ ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier);
+ return MessageProtections.None;
+ }
+
+ this.VerifyThrowTokenTimeToLive(authorizedTokenRequest);
+ }
+
+ var userAuthorizationRequest = message as UserAuthorizationRequest;
+ if (userAuthorizationRequest != null) {
+ this.VerifyThrowTokenTimeToLive(userAuthorizationRequest);
+ }
+
+ var accessResourceRequest = message as AccessProtectedResourceRequest;
+ if (accessResourceRequest != null) {
+ this.VerifyThrowTokenNotExpired(accessResourceRequest);
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Ensures that access tokens have not yet expired.
+ /// </summary>
+ /// <param name="message">The incoming message carrying the access token.</param>
+ private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) {
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+
+ try {
+ IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken);
+ if (token.ExpirationDate.HasValue && DateTime.Now >= token.ExpirationDate.Value.ToLocalTime()) {
+ Logger.OAuth.ErrorFormat(
+ "OAuth access token {0} rejected because it expired at {1}, and it is now {2}.",
+ token.Token,
+ token.ExpirationDate.Value,
+ DateTime.Now);
+ ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound);
+ }
+ } catch (KeyNotFoundException ex) {
+ throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound);
+ }
+ }
+
+ /// <summary>
+ /// Ensures that short-lived request tokens included in incoming messages have not expired.
+ /// </summary>
+ /// <param name="message">The incoming message.</param>
+ /// <exception cref="ProtocolException">Thrown when the token in the message has expired.</exception>
+ private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) {
+ ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens.");
+ if (message == null) {
+ return;
+ }
+
+ try {
+ IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token);
+ TimeSpan ttl = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.MaximumRequestTokenTimeToLive;
+ if (DateTime.Now >= token.CreatedOn.ToLocalTime() + ttl) {
+ Logger.OAuth.ErrorFormat(
+ "OAuth request token {0} rejected because it was originally issued at {1}, expired at {2}, and it is now {3}.",
+ token.Token,
+ token.CreatedOn,
+ token.CreatedOn + ttl,
+ DateTime.Now);
+ ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound);
+ }
+ } catch (KeyNotFoundException ex) {
+ throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs
new file mode 100644
index 0000000..5aedc9d
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriOrOobEncoding.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// An URI encoder that translates null <see cref="Uri"/> references as "oob"
+ /// instead of an empty/missing argument.
+ /// </summary>
+ internal class UriOrOobEncoding : IMessagePartNullEncoder {
+ /// <summary>
+ /// The string constant "oob", used to indicate an out-of-band configuration.
+ /// </summary>
+ private const string OutOfBandConfiguration = "oob";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriOrOobEncoding"/> class.
+ /// </summary>
+ public UriOrOobEncoding() {
+ }
+
+ #region IMessagePartNullEncoder Members
+
+ /// <summary>
+ /// Gets the string representation to include in a serialized message
+ /// when the message part has a <c>null</c> value.
+ /// </summary>
+ /// <value></value>
+ public string EncodedNullValue {
+ get { return OutOfBandConfiguration; }
+ }
+
+ #endregion
+
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ ErrorUtilities.VerifyArgumentNotNull(value, "value");
+
+ Uri uriValue = (Uri)value;
+ return uriValue.AbsoluteUri;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ if (string.Equals(value, OutOfBandConfiguration, StringComparison.Ordinal)) {
+ return null;
+ } else {
+ return new Uri(value, UriKind.Absolute);
+ }
+ }
+
+ #endregion
+ }
+}