summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth')
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs14
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs34
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs33
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthElement.cs48
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs34
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs53
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj15
-rw-r--r--src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs11
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs2
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs44
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs18
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs11
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs26
-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/IServiceProviderRequestToken.cs47
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs17
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs31
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs6
-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.cs125
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs78
-rw-r--r--src/DotNetOpenAuth/OAuth/ConsumerBase.cs35
-rw-r--r--src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs18
-rw-r--r--src/DotNetOpenAuth/OAuth/DesktopConsumer.cs21
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs6
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs13
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs3
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs18
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs7
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs18
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs15
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs20
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs19
-rw-r--r--src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs32
-rw-r--r--src/DotNetOpenAuth/OAuth/OAuthStrings.resx12
-rw-r--r--src/DotNetOpenAuth/OAuth/Protocol.cs105
-rw-r--r--src/DotNetOpenAuth/OAuth/SecuritySettings.cs18
-rw-r--r--src/DotNetOpenAuth/OAuth/ServiceProvider.cs120
-rw-r--r--src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs15
-rw-r--r--src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs23
-rw-r--r--src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs58
-rw-r--r--src/DotNetOpenAuth/OAuth/WebConsumer.cs5
-rw-r--r--src/DotNetOpenAuth/OpenId/Protocol.cs2
-rw-r--r--src/DotNetOpenAuth/Yadis/HtmlParser.cs4
46 files changed, 1210 insertions, 224 deletions
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs b/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs
index f535c38..7bd84d9 100644
--- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs
+++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs
@@ -30,6 +30,11 @@ namespace DotNetOpenAuth.Configuration {
private const string OpenIdElementName = "openid";
/// <summary>
+ /// The name of the &lt;oauth&gt; sub-element.
+ /// </summary>
+ private const string OAuthElementName = "oauth";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="DotNetOpenAuthSection"/> class.
/// </summary>
internal DotNetOpenAuthSection() {
@@ -61,5 +66,14 @@ namespace DotNetOpenAuth.Configuration {
get { return (OpenIdElement)this[OpenIdElementName] ?? new OpenIdElement(); }
set { this[OpenIdElementName] = value; }
}
+
+ /// <summary>
+ /// Gets or sets the configuration for OAuth.
+ /// </summary>
+ [ConfigurationProperty(OAuthElementName)]
+ internal OAuthElement OAuth {
+ get { return (OAuthElement)this[OAuthElementName] ?? new OAuthElement(); }
+ set { this[OAuthElementName] = value; }
+ }
}
}
diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs b/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs
new file mode 100644
index 0000000..b15c3e3
--- /dev/null
+++ b/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthConsumerElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+
+ /// <summary>
+ /// Represents the &lt;oauth/consumer&gt; element in the host's .config file.
+ /// </summary>
+ internal class OAuthConsumerElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the security sub-element.
+ /// </summary>
+ private const string SecuritySettingsConfigName = "security";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthConsumerElement"/> class.
+ /// </summary>
+ internal OAuthConsumerElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings.
+ /// </summary>
+ [ConfigurationProperty(SecuritySettingsConfigName)]
+ public OAuthConsumerSecuritySettingsElement SecuritySettings {
+ get { return (OAuthConsumerSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OAuthConsumerSecuritySettingsElement(); }
+ set { this[SecuritySettingsConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs
new file mode 100644
index 0000000..5e75390
--- /dev/null
+++ b/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthConsumerSecuritySettingsElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OAuth;
+
+ /// <summary>
+ /// Security settings that are applicable to consumers.
+ /// </summary>
+ internal class OAuthConsumerSecuritySettingsElement : ConfigurationElement {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthConsumerSecuritySettingsElement"/> class.
+ /// </summary>
+ internal OAuthConsumerSecuritySettingsElement() {
+ }
+
+ /// <summary>
+ /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
+ /// </summary>
+ /// <returns>The newly created security settings object.</returns>
+ internal ConsumerSecuritySettings CreateSecuritySettings() {
+ return new ConsumerSecuritySettings();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Configuration/OAuthElement.cs b/src/DotNetOpenAuth/Configuration/OAuthElement.cs
new file mode 100644
index 0000000..282bdba
--- /dev/null
+++ b/src/DotNetOpenAuth/Configuration/OAuthElement.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+
+ /// <summary>
+ /// Represents the &lt;oauth&gt; element in the host's .config file.
+ /// </summary>
+ internal class OAuthElement : ConfigurationElement {
+ /// <summary>
+ /// The name of the &lt;consumer&gt; sub-element.
+ /// </summary>
+ private const string ConsumerElementName = "consumer";
+
+ /// <summary>
+ /// The name of the &lt;serviceProvider&gt; sub-element.
+ /// </summary>
+ private const string ServiceProviderElementName = "serviceProvider";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthElement"/> class.
+ /// </summary>
+ internal OAuthElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration specific for Consumers.
+ /// </summary>
+ [ConfigurationProperty(ConsumerElementName)]
+ internal OAuthConsumerElement Consumer {
+ get { return (OAuthConsumerElement)this[ConsumerElementName] ?? new OAuthConsumerElement(); }
+ set { this[ConsumerElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration specific for Service Providers.
+ /// </summary>
+ [ConfigurationProperty(ServiceProviderElementName)]
+ internal OAuthServiceProviderElement ServiceProvider {
+ get { return (OAuthServiceProviderElement)this[ServiceProviderElementName] ?? new OAuthServiceProviderElement(); }
+ set { this[ServiceProviderElementName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs
new file mode 100644
index 0000000..5ff528d
--- /dev/null
+++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthServiceProviderElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+
+ /// <summary>
+ /// Represents the &lt;oauth/serviceProvider&gt; element in the host's .config file.
+ /// </summary>
+ internal class OAuthServiceProviderElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the security sub-element.
+ /// </summary>
+ private const string SecuritySettingsConfigName = "security";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthServiceProviderElement"/> class.
+ /// </summary>
+ internal OAuthServiceProviderElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings.
+ /// </summary>
+ [ConfigurationProperty(SecuritySettingsConfigName)]
+ public OAuthServiceProviderSecuritySettingsElement SecuritySettings {
+ get { return (OAuthServiceProviderSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OAuthServiceProviderSecuritySettingsElement(); }
+ set { this[SecuritySettingsConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs
new file mode 100644
index 0000000..92d9645
--- /dev/null
+++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthServiceProviderSecuritySettingsElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OAuth;
+
+ /// <summary>
+ /// Security settings that are applicable to service providers.
+ /// </summary>
+ internal class OAuthServiceProviderSecuritySettingsElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the @minimumRequiredOAuthVersion attribute.
+ /// </summary>
+ private const string MinimumRequiredOAuthVersionConfigName = "minimumRequiredOAuthVersion";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthServiceProviderSecuritySettingsElement"/> class.
+ /// </summary>
+ internal OAuthServiceProviderSecuritySettingsElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum OAuth version a Consumer is required to support in order for this library to interoperate with it.
+ /// </summary>
+ /// <remarks>
+ /// Although the earliest versions of OAuth are supported, for security reasons it may be desirable to require the
+ /// remote party to support a later version of OAuth.
+ /// </remarks>
+ [ConfigurationProperty(MinimumRequiredOAuthVersionConfigName, DefaultValue = "V10")]
+ public ProtocolVersion MinimumRequiredOAuthVersion {
+ get { return (ProtocolVersion)this[MinimumRequiredOAuthVersionConfigName]; }
+ set { this[MinimumRequiredOAuthVersionConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
+ /// </summary>
+ /// <returns>The newly created security settings object.</returns>
+ internal ServiceProviderSecuritySettings CreateSecuritySettings() {
+ return new ServiceProviderSecuritySettings {
+ MinimumRequiredOAuthVersion = this.MinimumRequiredOAuthVersion,
+ };
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 441c714..34c39a3 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -177,6 +177,11 @@
<Compile Include="Configuration\AssociationTypeElement.cs" />
<Compile Include="Configuration\DotNetOpenAuthSection.cs" />
<Compile Include="Configuration\MessagingElement.cs" />
+ <Compile Include="Configuration\OAuthConsumerElement.cs" />
+ <Compile Include="Configuration\OAuthConsumerSecuritySettingsElement.cs" />
+ <Compile Include="Configuration\OAuthElement.cs" />
+ <Compile Include="Configuration\OAuthServiceProviderElement.cs" />
+ <Compile Include="Configuration\OAuthServiceProviderSecuritySettingsElement.cs" />
<Compile Include="Configuration\OpenIdElement.cs" />
<Compile Include="Configuration\OpenIdProviderElement.cs" />
<Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" />
@@ -222,8 +227,9 @@
<Compile Include="Messaging\NetworkDirectWebResponse.cs" />
<Compile Include="Messaging\OutgoingWebResponseActionResult.cs" />
<Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" />
+ <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" />
<Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" />
- <Compile Include="OAuth\ChannelElements\IConsumerCertificateProvider.cs" />
+ <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" />
<Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" />
<Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" />
<Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" />
@@ -232,11 +238,15 @@
<Compile Include="OAuth\ChannelElements\OAuthHttpMethodBindingElement.cs" />
<Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElement.cs" />
<Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElement.cs" />
+ <Compile Include="OAuth\ChannelElements\IServiceProviderRequestToken.cs" />
<Compile Include="OAuth\ChannelElements\SigningBindingElementBaseContract.cs" />
<Compile Include="OAuth\ChannelElements\SigningBindingElementChain.cs" />
<Compile Include="OAuth\ChannelElements\StandardTokenGenerator.cs" />
<Compile Include="OAuth\ChannelElements\TokenType.cs" />
+ <Compile Include="OAuth\ChannelElements\UriOrOobEncoding.cs" />
+ <Compile Include="OAuth\ChannelElements\TokenHandlingBindingElement.cs" />
<Compile Include="OAuth\ConsumerBase.cs" />
+ <Compile Include="OAuth\ConsumerSecuritySettings.cs" />
<Compile Include="OAuth\DesktopConsumer.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="OAuth\Messages\ITokenSecretContainingMessage.cs" />
@@ -247,11 +257,14 @@
<DesignTime>True</DesignTime>
<DependentUpon>OAuthStrings.resx</DependentUpon>
</Compile>
+ <Compile Include="OAuth\SecuritySettings.cs" />
<Compile Include="OAuth\ServiceProviderDescription.cs" />
<Compile Include="OAuth\Messages\ITokenContainingMessage.cs" />
<Compile Include="OAuth\Messages\SignedMessageBase.cs" />
<Compile Include="Messaging\Bindings\NonceMemoryStore.cs" />
<Compile Include="OAuth\ChannelElements\SigningBindingElementBase.cs" />
+ <Compile Include="OAuth\ServiceProviderSecuritySettings.cs" />
+ <Compile Include="OAuth\VerificationCodeFormat.cs" />
<Compile Include="OAuth\WebConsumer.cs" />
<Compile Include="Messaging\IDirectWebRequestHandler.cs" />
<Compile Include="OAuth\ChannelElements\ITamperResistantOAuthMessage.cs" />
diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs
index bb2b28a..c8d5873 100644
--- a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs
+++ b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs
@@ -28,11 +28,6 @@ namespace DotNetOpenAuth.Messaging.Bindings {
private int nonceLength = 8;
/// <summary>
- /// A random number generator.
- /// </summary>
- private Random generator = new Random();
-
- /// <summary>
/// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class.
/// </summary>
/// <param name="nonceStore">The store where nonces will be persisted and checked.</param>
@@ -145,11 +140,7 @@ namespace DotNetOpenAuth.Messaging.Bindings {
/// </summary>
/// <returns>The nonce string.</returns>
private string GenerateUniqueFragment() {
- char[] nonce = new char[this.nonceLength];
- for (int i = 0; i < nonce.Length; i++) {
- nonce[i] = AllowedCharacters[this.generator.Next(AllowedCharacters.Length)];
- }
- return new string(nonce);
+ return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters);
}
}
}
diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
index 82910d5..22c660c 100644
--- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.Messaging {
using System;
+ using System.Diagnostics;
using System.Net.Security;
using System.Reflection;
@@ -13,6 +14,7 @@ namespace DotNetOpenAuth.Messaging {
/// Applied to fields and properties that form a key/value in a protocol message.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = true)]
+ [DebuggerDisplay("MessagePartAttribute {Name}")]
public sealed class MessagePartAttribute : Attribute {
/// <summary>
/// The overridden name to use as the serialized name for the property.
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index 10ddba0..78342ae 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -31,6 +31,32 @@ namespace DotNetOpenAuth.Messaging {
internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider();
/// <summary>
+ /// A pseudo-random data generator (NOT cryptographically strong random data)
+ /// </summary>
+ internal static readonly Random NonCryptoRandomDataGenerator = new Random();
+
+ /// <summary>
+ /// The uppercase alphabet.
+ /// </summary>
+ internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ /// <summary>
+ /// The lowercase alphabet.
+ /// </summary>
+ internal const string LowercaseLetters = "abcdefghijklmnopqrstuvwxyz";
+
+ /// <summary>
+ /// The set of base 10 digits.
+ /// </summary>
+ internal const string Digits = "0123456789";
+
+ /// <summary>
+ /// The set of digits, and alphabetic letters (upper and lowercase) that are clearly
+ /// visually distinguishable.
+ /// </summary>
+ internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ";
+
+ /// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
@@ -163,6 +189,24 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets a random string made up of a given set of allowable characters.
+ /// </summary>
+ /// <param name="length">The length of the desired random string.</param>
+ /// <param name="allowableCharacters">The allowable characters.</param>
+ /// <returns>A random string.</returns>
+ internal static string GetRandomString(int length, string allowableCharacters) {
+ Contract.Requires(length >= 0);
+ Contract.Requires(allowableCharacters != null && allowableCharacters.Length >= 2);
+
+ char[] randomString = new char[length];
+ for (int i = 0; i < length; i++) {
+ randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)];
+ }
+
+ return new string(randomString);
+ }
+
+ /// <summary>
/// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance,
/// taking care to set some headers to the appropriate properties of
/// <see cref="HttpResponse" />
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs
new file mode 100644
index 0000000..7581550
--- /dev/null
+++ b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="IMessagePartNullEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging.Reflection {
+ /// <summary>
+ /// A message part encoder that has a special encoding for a null value.
+ /// </summary>
+ public interface IMessagePartNullEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// Gets the string representation to include in a serialized message
+ /// when the message part has a <c>null</c> value.
+ /// </summary>
+ string EncodedNullValue { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
index 0e26860..0732bb2 100644
--- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
+++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
@@ -7,6 +7,7 @@
namespace DotNetOpenAuth.Messaging.Reflection {
using System;
using System.Collections.Generic;
+ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Security;
@@ -17,6 +18,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// <summary>
/// Describes an individual member of a message and assists in its serialization.
/// </summary>
+ [DebuggerDisplay("MessagePart {Name}")]
internal class MessagePart {
/// <summary>
/// A map of converters that help serialize custom objects to string values and back again.
@@ -113,10 +115,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null);
}
} else {
- var encoder = GetEncoder(attribute.Encoder);
- this.converter = new ValueMapping(
- obj => encoder.Encode(obj),
- str => encoder.Decode(str));
+ this.converter = new ValueMapping(GetEncoder(attribute.Encoder));
}
// readonly and const fields are considered legal, and "constants" for message transport.
@@ -286,7 +285,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// An instance of the appropriate type for setting the member.
/// </returns>
private object ToValue(string value) {
- return value == null ? null : this.converter.StringToValue(value);
+ return this.converter.StringToValue(value);
}
/// <summary>
@@ -297,7 +296,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// The string representation of the member's value.
/// </returns>
private string ToString(object value) {
- return value == null ? null : this.converter.ValueToString(value);
+ return this.converter.ValueToString(value);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs
index bdc2d7f..332274e 100644
--- a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs
+++ b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs
@@ -14,12 +14,12 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// <summary>
/// The mapping function that converts some custom type to a string.
/// </summary>
- internal Func<object, string> ValueToString;
+ internal readonly Func<object, string> ValueToString;
/// <summary>
/// The mapping function that converts a string to some custom type.
/// </summary>
- internal Func<string, object> StringToValue;
+ internal readonly Func<string, object> StringToValue;
/// <summary>
/// Initializes a new instance of the <see cref="ValueMapping"/> struct.
@@ -27,16 +27,24 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// <param name="toString">The mapping function that converts some custom type to a string.</param>
/// <param name="toValue">The mapping function that converts a string to some custom type.</param>
internal ValueMapping(Func<object, string> toString, Func<string, object> toValue) {
- if (toString == null) {
- throw new ArgumentNullException("toString");
- }
-
- if (toValue == null) {
- throw new ArgumentNullException("toValue");
- }
+ ErrorUtilities.VerifyArgumentNotNull(toString, "toString");
+ ErrorUtilities.VerifyArgumentNotNull(toValue, "toValue");
this.ValueToString = toString;
this.StringToValue = toValue;
}
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ValueMapping"/> struct.
+ /// </summary>
+ /// <param name="encoder">The encoder.</param>
+ internal ValueMapping(IMessagePartEncoder encoder) {
+ ErrorUtilities.VerifyArgumentNotNull(encoder, "encoder");
+ var nullEncoder = encoder as IMessagePartNullEncoder;
+ string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null;
+
+ this.ValueToString = obj => (obj != null) ? encoder.Encode(obj) : nullString;
+ this.StringToValue = str => (str != null) ? encoder.Decode(str) : null;
+ }
}
}
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/IServiceProviderRequestToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs
new file mode 100644
index 0000000..48f39a6
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <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 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..fa008ac 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs
@@ -16,12 +16,19 @@ 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>
+ IServiceProviderRequestToken GetRequestToken(string token);
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index 5ac1aa8..2487845 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)) {
ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager");
this.TokenManager = tokenManager;
@@ -122,7 +122,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.
@@ -241,6 +241,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>
@@ -307,7 +330,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);
@@ -366,7 +389,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 fce351b..e05bb62 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs
@@ -44,7 +44,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) {
@@ -92,7 +93,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/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
index 1aaea7f..4727a6d 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs
@@ -54,29 +54,51 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
ErrorUtilities.VerifyArgumentNotNull(fields, "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..b8273c3
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs
@@ -0,0 +1,125 @@
+//-----------------------------------------------------------------------
+// <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.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 && 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;
+ }
+
+ return null;
+ }
+
+ #endregion
+ }
+}
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
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
index 2392172..8c4484b 100644
--- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
+++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
@@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OAuth {
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Net;
+ using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OAuth.ChannelElements;
@@ -32,6 +33,7 @@ namespace DotNetOpenAuth.OAuth {
INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager);
this.ServiceProvider = serviceDescription;
+ this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings();
}
/// <summary>
@@ -61,6 +63,11 @@ namespace DotNetOpenAuth.OAuth {
}
/// <summary>
+ /// Gets the security settings for this consumer.
+ /// </summary>
+ internal ConsumerSecuritySettings SecuritySettings { get; private set; }
+
+ /// <summary>
/// Gets or sets the channel to use for sending/receiving messages.
/// </summary>
internal OAuthChannel OAuthChannel { get; set; }
@@ -167,7 +174,7 @@ namespace DotNetOpenAuth.OAuth {
ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint");
ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken");
- AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint) {
+ AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) {
AccessToken = accessToken,
ConsumerKey = this.ConsumerKey,
};
@@ -189,18 +196,26 @@ namespace DotNetOpenAuth.OAuth {
/// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")]
protected internal UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) {
- // Obtain an unauthorized request token.
- var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint) {
+ // Obtain an unauthorized request token. Assume the OAuth version given in the service description.
+ var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) {
ConsumerKey = this.ConsumerKey,
+ Callback = callback,
};
var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token);
tokenAccessor.AddExtraParameters(requestParameters);
var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token);
this.TokenManager.StoreNewRequestToken(token, requestTokenResponse);
- // Request user authorization.
+ // Fine-tune our understanding of the SP's supported OAuth version if it's wrong.
+ if (this.ServiceProvider.Version != requestTokenResponse.Version) {
+ Logger.OAuth.WarnFormat("Expected OAuth service provider at endpoint {0} to use OAuth {1} but {2} was detected. Adjusting service description to new version.", this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version, requestTokenResponse.Version);
+ this.ServiceProvider.ProtocolVersion = Protocol.Lookup(requestTokenResponse.Version).ProtocolVersion;
+ }
+
+ // Request user authorization. The OAuth version will automatically include
+ // or drop the callback that we're setting here.
ITokenContainingMessage assignedRequestToken = requestTokenResponse;
- var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token) {
+ var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, requestTokenResponse.Version) {
Callback = callback,
};
var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization);
@@ -213,10 +228,14 @@ namespace DotNetOpenAuth.OAuth {
/// Exchanges a given request token for access token.
/// </summary>
/// <param name="requestToken">The request token that the user has authorized.</param>
- /// <returns>The access token assigned by the Service Provider.</returns>
- protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) {
- var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint) {
+ /// <param name="verifier">The verifier code.</param>
+ /// <returns>
+ /// The access token assigned by the Service Provider.
+ /// </returns>
+ protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) {
+ var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) {
RequestToken = requestToken,
+ VerificationCode = verifier,
ConsumerKey = this.ConsumerKey,
};
var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess);
diff --git a/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs b/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs
new file mode 100644
index 0000000..bb2fbaa
--- /dev/null
+++ b/src/DotNetOpenAuth/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/DesktopConsumer.cs b/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs
index ca74a77..f9c1a94 100644
--- a/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs
+++ b/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs
@@ -49,8 +49,25 @@ namespace DotNetOpenAuth.OAuth {
/// </summary>
/// <param name="requestToken">The request token that the user has authorized.</param>
/// <returns>The access token assigned by the Service Provider.</returns>
- public new AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) {
- return base.ProcessUserAuthorization(requestToken);
+ [Obsolete("Use the ProcessUserAuthorization method that takes a verifier parameter instead.")]
+ public AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) {
+ return this.ProcessUserAuthorization(requestToken, null);
+ }
+
+ /// <summary>
+ /// Exchanges a given request token for access token.
+ /// </summary>
+ /// <param name="requestToken">The request token that the user has authorized.</param>
+ /// <param name="verifier">The verifier code typed in by the user. Must not be <c>Null</c> for OAuth 1.0a service providers and later.</param>
+ /// <returns>
+ /// The access token assigned by the Service Provider.
+ /// </returns>
+ public new AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) {
+ if (this.ServiceProvider.Version >= Protocol.V10a.Version) {
+ ErrorUtilities.VerifyNonZeroLength(verifier, "verifier");
+ }
+
+ return base.ProcessUserAuthorization(requestToken, verifier);
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
index 62e02de..b60fda4 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
using System.Diagnostics.CodeAnalysis;
using DotNetOpenAuth.Messaging;
@@ -17,8 +18,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// Initializes a new instance of the <see cref="AccessProtectedResourceRequest"/> class.
/// </summary>
/// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
- protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider)
- : base(MessageTransport.Direct, serviceProvider) {
+ /// <param name="version">The OAuth version.</param>
+ protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageTransport.Direct, serviceProvider, version) {
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs
index 2d4793c..1228290 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
using System.Globalization;
using DotNetOpenAuth.Messaging;
@@ -20,8 +21,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// Initializes a new instance of the <see cref="AuthorizedTokenRequest"/> class.
/// </summary>
/// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
- internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider)
- : base(MessageTransport.Direct, serviceProvider) {
+ /// <param name="version">The OAuth version.</param>
+ internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageTransport.Direct, serviceProvider, version) {
}
/// <summary>
@@ -33,6 +35,13 @@ namespace DotNetOpenAuth.OAuth.Messages {
}
/// <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 unauthorized Request Token used to obtain authorization.
/// </summary>
[MessagePart("oauth_token", IsRequired = true)]
diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs
index 14413a5..0b14819 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using DotNetOpenAuth.Messaging;
@@ -19,7 +20,7 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// </summary>
/// <param name="originatingRequest">The originating request.</param>
protected internal AuthorizedTokenResponse(AuthorizedTokenRequest originatingRequest)
- : base(MessageProtections.None, originatingRequest) {
+ : base(MessageProtections.None, originatingRequest, originatingRequest.Version) {
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs
index e0269db..89e0276 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs
@@ -62,12 +62,15 @@ namespace DotNetOpenAuth.OAuth.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>
- protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest) {
+ /// <param name="version">The OAuth version.</param>
+ protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) {
ErrorUtilities.VerifyArgumentNotNull(originatingRequest, "originatingRequest");
+ ErrorUtilities.VerifyArgumentNotNull(version, "version");
this.protectionRequired = protectionRequired;
this.transport = MessageTransport.Direct;
this.originatingRequest = originatingRequest;
+ this.Version = version;
}
/// <summary>
@@ -76,14 +79,15 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// <param name="protectionRequired">The level of protection the message requires.</param>
/// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param>
/// <param name="recipient">The URI that a directed message will be delivered to.</param>
- protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient) {
- if (recipient == null) {
- throw new ArgumentNullException("recipient");
- }
+ /// <param name="version">The OAuth version.</param>
+ protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) {
+ ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient");
+ ErrorUtilities.VerifyArgumentNotNull(version, "version");
this.protectionRequired = protectionRequired;
this.transport = transport;
this.recipient = recipient;
+ this.Version = version;
}
#region IProtocolMessage Properties
@@ -163,9 +167,7 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// <summary>
/// Gets the version of the protocol this message is prepared to implement.
/// </summary>
- protected virtual Version Version {
- get { return new Version(1, 0); }
- }
+ protected internal Version Version { get; private set; }
/// <summary>
/// Gets the level of protection this message requires.
diff --git a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs
index d1abb58..1d8ca21 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs
@@ -31,8 +31,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// </summary>
/// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param>
/// <param name="recipient">The URI that a directed message will be delivered to.</param>
- internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient)
- : base(MessageProtections.All, transport, recipient) {
+ /// <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 = (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET";
@@ -164,7 +165,7 @@ namespace DotNetOpenAuth.OAuth.Messages {
[MessagePart("oauth_version", IsRequired = false)]
private string OAuthVersion {
get {
- return Version.ToString();
+ return Protocol.Lookup(Version).PublishedVersion;
}
set {
diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs
index e491bad..9214d91 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs
@@ -5,8 +5,10 @@
//-----------------------------------------------------------------------
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.
@@ -16,11 +18,23 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// Initializes a new instance of the <see cref="UnauthorizedTokenRequest"/> class.
/// </summary>
/// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
- protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider)
- : base(MessageTransport.Direct, serviceProvider) {
+ /// <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 {
diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs
index 285dec7..8ccd8e3 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs
@@ -25,7 +25,7 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// This constructor is used by the Service Provider to send the message.
/// </remarks>
protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret)
- : this(requestMessage) {
+ : this(requestMessage, requestMessage.Version) {
ErrorUtilities.VerifyArgumentNotNull(requestToken, "requestToken");
ErrorUtilities.VerifyArgumentNotNull(tokenSecret, "tokenSecret");
@@ -37,9 +37,10 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// 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)
- : base(MessageProtections.None, originatingRequest) {
+ protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version)
+ : base(MessageProtections.None, originatingRequest, version) {
}
/// <summary>
@@ -84,5 +85,13 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// </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>
+ [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)]
+ private bool CallbackConfirmed {
+ get { return true; }
+ }
}
}
diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs
index f1af0bc..099729e 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs
@@ -21,8 +21,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// </summary>
/// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
/// <param name="requestToken">The request token.</param>
- internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken)
- : this(serviceProvider) {
+ /// <param name="version">The OAuth version.</param>
+ internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken, Version version)
+ : this(serviceProvider, version) {
this.RequestToken = requestToken;
}
@@ -30,8 +31,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// Initializes a new instance of the <see cref="UserAuthorizationRequest"/> class.
/// </summary>
/// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
- internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider)
- : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider) {
+ /// <param name="version">The OAuth version.</param>
+ internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider, version) {
}
/// <summary>
@@ -51,6 +53,14 @@ namespace DotNetOpenAuth.OAuth.Messages {
}
/// <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>
@@ -65,7 +75,7 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// 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)]
+ [MessagePart("oauth_callback", IsRequired = false, MaxVersion = "1.0")]
internal Uri Callback { get; set; }
}
}
diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs
index da6a909..69a327c 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs
@@ -19,8 +19,9 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// Initializes a new instance of the <see cref="UserAuthorizationResponse"/> class.
/// </summary>
/// <param name="consumer">The URI of the Consumer endpoint to send this message to.</param>
- internal UserAuthorizationResponse(Uri consumer)
- : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest)) {
+ /// <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>
@@ -32,6 +33,20 @@ namespace DotNetOpenAuth.OAuth.Messages {
}
/// <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)]
diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
index 63e348a..689998a 100644
--- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.3521
+// Runtime Version:2.0.50727.4918
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -79,20 +79,20 @@ namespace DotNetOpenAuth.OAuth {
}
/// <summary>
- /// Looks up a localized string similar to The RSA-SHA1 signing binding element&apos;s consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified..
+ /// Looks up a localized string similar to Failure looking up secret for consumer or token..
/// </summary>
- internal static string ConsumerCertificateProviderNotAvailable {
+ internal static string ConsumerOrTokenSecretNotFound {
get {
- return ResourceManager.GetString("ConsumerCertificateProviderNotAvailable", resourceCulture);
+ return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Failure looking up secret for consumer or token..
+ /// Looks up a localized string similar to oauth_verifier argument was incorrect..
/// </summary>
- internal static string ConsumerOrTokenSecretNotFound {
+ internal static string IncorrectVerifier {
get {
- return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture);
+ return ResourceManager.GetString("IncorrectVerifier", resourceCulture);
}
}
@@ -115,6 +115,15 @@ namespace DotNetOpenAuth.OAuth {
}
/// <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 The request URL query MUST NOT contain any OAuth Protocol Parameters..
/// </summary>
internal static string RequestUrlMustNotHaveOAuthParameters {
@@ -142,6 +151,15 @@ namespace DotNetOpenAuth.OAuth {
}
/// <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 {
diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
index 3ba4da1..a40b35d 100644
--- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
+++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
@@ -123,18 +123,21 @@
<data name="BadAccessTokenInProtectedResourceRequest" xml:space="preserve">
<value>The access token '{0}' is invalid or expired.</value>
</data>
- <data name="ConsumerCertificateProviderNotAvailable" xml:space="preserve">
- <value>The RSA-SHA1 signing binding element's consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified.</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="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve">
<value>The request URL query MUST NOT contain any OAuth Protocol Parameters.</value>
</data>
@@ -144,6 +147,9 @@
<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>
diff --git a/src/DotNetOpenAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth/OAuth/Protocol.cs
index 88615ff..f535b10 100644
--- a/src/DotNetOpenAuth/OAuth/Protocol.cs
+++ b/src/DotNetOpenAuth/OAuth/Protocol.cs
@@ -7,17 +7,34 @@
namespace DotNetOpenAuth.OAuth {
using System;
using System.Collections.Generic;
+ using System.Diagnostics;
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>
+ 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.
@@ -25,63 +42,105 @@ namespace DotNetOpenAuth.OAuth {
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 OpenID 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>
- /// The prefix used for all key names in the protocol.
+ /// Initializes a new instance of the <see cref="Protocol"/> class.
/// </summary>
- private string parameterPrefix = "oauth_";
+ internal Protocol() {
+ this.PublishedVersion = "1.0";
+ }
/// <summary>
- /// The scheme to use in Authorization header message requests.
+ /// Gets the version used to represent OAuth 1.0a.
/// </summary>
- private string authorizationHeaderScheme = "OAuth";
+ internal Version Version { get; private set; }
/// <summary>
- /// Gets the default <see cref="Protocol"/> instance.
+ /// Gets the version to declare on the wire.
/// </summary>
- internal static Protocol Default { get { return V10; } }
+ internal string PublishedVersion { get; private set; }
/// <summary>
- /// Gets the namespace to use for this version of the protocol.
+ /// Gets the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance.
/// </summary>
- internal string DataContractNamespace {
- get { return this.dataContractNamespace; }
- }
+ internal ProtocolVersion ProtocolVersion { get; private set; }
/// <summary>
- /// Gets the prefix used for all key names in the protocol.
+ /// Gets the namespace to use for this version of the protocol.
/// </summary>
- internal string ParameterPrefix {
- get { return this.parameterPrefix; }
+ internal string DataContractNamespace {
+ get { return this.dataContractNamespace; }
}
/// <summary>
- /// Gets the scheme to use in Authorization header message requests.
+ /// Gets the OAuth Protocol instance to use for the given version.
/// </summary>
- internal string AuthorizationHeaderScheme {
- get { return this.authorizationHeaderScheme; }
+ /// <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 an instance of <see cref="Protocol"/> given a <see cref="Version"/>.
+ /// Gets the OAuth Protocol instance to use for the given version.
/// </summary>
- /// <param name="version">The version of the protocol that is desired.</param>
- /// <returns>The <see cref="Protocol"/> instance representing the requested version.</returns>
+ /// <param name="version">The OAuth version to get.</param>
+ /// <returns>A matching <see cref="Protocol"/> instance.</returns>
internal static Protocol Lookup(Version version) {
- switch (version.Major) {
- case 1: return Protocol.V10;
- default: throw new ArgumentOutOfRangeException("version");
- }
+ ErrorUtilities.VerifyArgumentNotNull(version, "version");
+ Protocol protocol = AllVersions.FirstOrDefault(p => p.Version == version);
+ ErrorUtilities.VerifyArgumentInRange(protocol != null, "version");
+ return protocol;
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth/SecuritySettings.cs b/src/DotNetOpenAuth/OAuth/SecuritySettings.cs
new file mode 100644
index 0000000..3329f09
--- /dev/null
+++ b/src/DotNetOpenAuth/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/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs
index 345c6a2..d4fe85e 100644
--- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs
+++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs
@@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OAuth {
using System.Globalization;
using System.ServiceModel.Channels;
using System.Web;
+ using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OAuth.ChannelElements;
@@ -29,6 +30,11 @@ namespace DotNetOpenAuth.OAuth {
/// </remarks>
public class ServiceProvider : IDisposable {
/// <summary>
+ /// The length of the verifier code (in raw bytes before base64 encoding) to generate.
+ /// </summary>
+ private const int VerifierCodeLength = 5;
+
+ /// <summary>
/// The field behind the <see cref="OAuthChannel"/> property.
/// </summary>
private OAuthChannel channel;
@@ -48,7 +54,7 @@ namespace DotNetOpenAuth.OAuth {
/// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param>
/// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
/// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param>
- public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) {
+ public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) {
ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription");
ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager");
ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider");
@@ -58,6 +64,7 @@ namespace DotNetOpenAuth.OAuth {
this.ServiceDescription = serviceDescription;
this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, messageTypeProvider);
this.TokenGenerator = new StandardTokenGenerator();
+ this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings();
}
/// <summary>
@@ -73,8 +80,8 @@ namespace DotNetOpenAuth.OAuth {
/// <summary>
/// Gets the persistence store for tokens and secrets.
/// </summary>
- public ITokenManager TokenManager {
- get { return this.OAuthChannel.TokenManager; }
+ public IServiceProviderTokenManager TokenManager {
+ get { return (IServiceProviderTokenManager)this.OAuthChannel.TokenManager; }
}
/// <summary>
@@ -85,6 +92,11 @@ namespace DotNetOpenAuth.OAuth {
}
/// <summary>
+ /// Gets the security settings for this service provider.
+ /// </summary>
+ public ServiceProviderSecuritySettings SecuritySettings { get; private set; }
+
+ /// <summary>
/// Gets or sets the channel to use for sending/receiving messages.
/// </summary>
internal OAuthChannel OAuthChannel {
@@ -93,15 +105,38 @@ namespace DotNetOpenAuth.OAuth {
}
set {
- if (this.channel != null) {
- this.channel.Sending -= this.OAuthChannel_Sending;
- }
-
+ Contract.Requires(value != null);
+ ErrorUtilities.VerifyArgumentNotNull(value, "value");
this.channel = value;
+ }
+ }
- if (this.channel != null) {
- this.channel.Sending += this.OAuthChannel_Sending;
- }
+ /// <summary>
+ /// Creates a cryptographically strong random verification code.
+ /// </summary>
+ /// <param name="format">The desired format of the verification code.</param>
+ /// <param name="length">The length of the code.
+ /// When <paramref name="format"/> is <see cref="VerificationCodeFormat.IncludedInCallback"/>,
+ /// this is the length of the original byte array before base64 encoding rather than the actual
+ /// length of the final string.</param>
+ /// <returns>The verification code.</returns>
+ public static string CreateVerificationCode(VerificationCodeFormat format, int length) {
+ Contract.Requires(length >= 0);
+ ErrorUtilities.VerifyArgumentInRange(length >= 0, "length");
+
+ switch (format) {
+ case VerificationCodeFormat.IncludedInCallback:
+ return MessagingUtilities.GetCryptoRandomDataAsBase64(length);
+ case VerificationCodeFormat.AlphaNumericNoLookAlikes:
+ return MessagingUtilities.GetRandomString(length, MessagingUtilities.AlphaNumericNoLookAlikes);
+ case VerificationCodeFormat.AlphaUpper:
+ return MessagingUtilities.GetRandomString(length, MessagingUtilities.UppercaseLetters);
+ case VerificationCodeFormat.AlphaLower:
+ return MessagingUtilities.GetRandomString(length, MessagingUtilities.LowercaseLetters);
+ case VerificationCodeFormat.Numeric:
+ return MessagingUtilities.GetRandomString(length, MessagingUtilities.Digits);
+ default:
+ throw new ArgumentOutOfRangeException("format");
}
}
@@ -145,7 +180,9 @@ namespace DotNetOpenAuth.OAuth {
/// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) {
UnauthorizedTokenRequest message;
- this.Channel.TryReadFromRequest(request, out message);
+ if (this.Channel.TryReadFromRequest(request, out message)) {
+ ErrorUtilities.VerifyProtocol(message.Version >= Protocol.Lookup(this.SecuritySettings.MinimumRequiredOAuthVersion).Version, OAuthStrings.MinimumConsumerVersionRequirementNotMet, this.SecuritySettings.MinimumRequiredOAuthVersion, message.Version);
+ }
return message;
}
@@ -156,9 +193,7 @@ namespace DotNetOpenAuth.OAuth {
/// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param>
/// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns>
public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) {
- if (request == null) {
- throw new ArgumentNullException("request");
- }
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey);
string secret = this.TokenGenerator.GenerateSecret();
@@ -207,11 +242,27 @@ namespace DotNetOpenAuth.OAuth {
Contract.Requires(request != null);
ErrorUtilities.VerifyArgumentNotNull(request, "request");
- if (request.Callback != null) {
- return this.PrepareAuthorizationResponse(request, request.Callback);
+ // It is very important for us to ignore the oauth_callback argument in the
+ // UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we
+ // open up a security exploit.
+ IServiceProviderRequestToken token = this.TokenManager.GetRequestToken(request.RequestToken);
+ Uri callback;
+ if (request.Version >= Protocol.V10a.Version) {
+ // In OAuth 1.0a, we'll prefer the token-specific callback to the pre-registered one.
+ if (token.Callback != null) {
+ callback = token.Callback;
+ } else {
+ IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey);
+ callback = consumer.Callback;
+ }
} else {
- return null;
+ // In OAuth 1.0, we'll prefer the pre-registered callback over the token-specific one
+ // since 1.0 has a security weakness for user-modified callback URIs.
+ IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey);
+ callback = consumer.Callback ?? request.Callback;
}
+
+ return callback != null ? this.PrepareAuthorizationResponse(request, callback) : null;
}
/// <summary>
@@ -220,7 +271,7 @@ namespace DotNetOpenAuth.OAuth {
/// </summary>
/// <param name="request">The Consumer's original authorization request.</param>
/// <param name="callback">The callback URI the consumer has previously registered
- /// with this service provider.</param>
+ /// with this service provider or that came in the <see cref="UnauthorizedTokenRequest"/>.</param>
/// <returns>
/// The message to send to the Consumer using <see cref="Channel"/>.
/// </returns>
@@ -231,9 +282,14 @@ namespace DotNetOpenAuth.OAuth {
ErrorUtilities.VerifyArgumentNotNull(request, "request");
ErrorUtilities.VerifyArgumentNotNull(callback, "callback");
- var authorization = new UserAuthorizationResponse(request.Callback) {
+ var authorization = new UserAuthorizationResponse(callback, request.Version) {
RequestToken = request.RequestToken,
};
+
+ if (authorization.Version >= Protocol.V10a.Version) {
+ authorization.VerificationCode = CreateVerificationCode(VerificationCodeFormat.IncludedInCallback, VerifierCodeLength);
+ }
+
return authorization;
}
@@ -267,17 +323,10 @@ namespace DotNetOpenAuth.OAuth {
/// <param name="request">The Consumer's message requesting an access token.</param>
/// <returns>The HTTP response to actually send to the Consumer.</returns>
public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) {
- if (request == null) {
- throw new ArgumentNullException("request");
- }
+ Contract.Requires(request != null);
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
- if (!this.TokenManager.IsRequestTokenAuthorized(request.RequestToken)) {
- throw new ProtocolException(
- string.Format(
- CultureInfo.CurrentCulture,
- OAuthStrings.AccessTokenNotAuthorized,
- request.RequestToken));
- }
+ ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken);
string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey);
string tokenSecret = this.TokenGenerator.GenerateSecret();
@@ -369,18 +418,5 @@ namespace DotNetOpenAuth.OAuth {
}
#endregion
-
- /// <summary>
- /// Hooks the channel in order to perform some operations on some outgoing messages.
- /// </summary>
- /// <param name="sender">The source of the event.</param>
- /// <param name="e">The <see cref="DotNetOpenAuth.Messaging.ChannelEventArgs"/> instance containing the event data.</param>
- private void OAuthChannel_Sending(object sender, ChannelEventArgs e) {
- // Hook to store the token and secret on its way down to the Consumer.
- var grantRequestTokenResponse = e.Message as UnauthorizedTokenResponse;
- if (grantRequestTokenResponse != null) {
- this.TokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse);
- }
- }
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs
index 4636829..9014762 100644
--- a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs
+++ b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs
@@ -26,9 +26,15 @@ namespace DotNetOpenAuth.OAuth {
/// 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>
@@ -43,7 +49,7 @@ namespace DotNetOpenAuth.OAuth {
}
set {
- if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.V10.ParameterPrefix)) {
+ if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.ParameterPrefix)) {
throw new ArgumentException(OAuthStrings.RequestUrlMustNotHaveOAuthParameters);
}
@@ -77,6 +83,13 @@ namespace DotNetOpenAuth.OAuth {
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>
diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs
new file mode 100644
index 0000000..094f54c
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderSecuritySettings.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ /// <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; }
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs b/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs
new file mode 100644
index 0000000..d25c988
--- /dev/null
+++ b/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------
+// <copyright file="VerificationCodeFormat.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ /// <summary>
+ /// The different formats a user authorization verifier code can take
+ /// in order to be as secure as possible while being compatible with
+ /// the type of OAuth Consumer requesting access.
+ /// </summary>
+ /// <remarks>
+ /// Some Consumers may be set-top boxes, video games, mobile devies, etc.
+ /// with very limited character entry support and no ability to receive
+ /// a callback URI. OAuth 1.0a requires that these devices operators
+ /// must manually key in a verifier code, so in these cases it better
+ /// be possible to do so given the input options on that device.
+ /// </remarks>
+ public enum VerificationCodeFormat {
+ /// <summary>
+ /// The strongest verification code.
+ /// The best option for web consumers since a callback is usually an option.
+ /// </summary>
+ IncludedInCallback,
+
+ /// <summary>
+ /// A combination of upper and lowercase letters and numbers may be used,
+ /// allowing a computer operator to easily read from the screen and key
+ /// in the verification code.
+ /// </summary>
+ /// <remarks>
+ /// Some letters and numbers will be skipped where they are visually similar
+ /// enough that they can be difficult to distinguish when displayed with most fonts.
+ /// </remarks>
+ AlphaNumericNoLookAlikes,
+
+ /// <summary>
+ /// Only uppercase letters will be used in the verification code.
+ /// Verification codes are case-sensitive, so consumers with fixed
+ /// keyboards with only one character case option may require this option.
+ /// </summary>
+ AlphaUpper,
+
+ /// <summary>
+ /// Only lowercase letters will be used in the verification code.
+ /// Verification codes are case-sensitive, so consumers with fixed
+ /// keyboards with only one character case option may require this option.
+ /// </summary>
+ AlphaLower,
+
+ /// <summary>
+ /// Only the numbers 0-9 will be used in the verification code.
+ /// Must useful for consumers running on mobile phone devices.
+ /// </summary>
+ Numeric,
+ }
+}
diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs
index bbf6115..11f3da8 100644
--- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs
+++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs
@@ -39,7 +39,7 @@ namespace DotNetOpenAuth.OAuth {
/// Requires HttpContext.Current.
/// </remarks>
public UserAuthorizationRequest PrepareRequestUserAuthorization() {
- Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.Default.ParameterPrefix);
+ Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.ParameterPrefix);
return this.PrepareRequestUserAuthorization(callback, null, null);
}
@@ -79,7 +79,8 @@ namespace DotNetOpenAuth.OAuth {
UserAuthorizationResponse authorizationMessage;
if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) {
string requestToken = authorizationMessage.RequestToken;
- return this.ProcessUserAuthorization(requestToken);
+ string verifier = authorizationMessage.VerificationCode;
+ return this.ProcessUserAuthorization(requestToken, verifier);
} else {
return null;
}
diff --git a/src/DotNetOpenAuth/OpenId/Protocol.cs b/src/DotNetOpenAuth/OpenId/Protocol.cs
index b9f2cca..7b8a2f1 100644
--- a/src/DotNetOpenAuth/OpenId/Protocol.cs
+++ b/src/DotNetOpenAuth/OpenId/Protocol.cs
@@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OpenId {
using DotNetOpenAuth.Messaging;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics;
/// <summary>
/// An enumeration of the OpenID protocol versions supported by this library.
@@ -34,6 +35,7 @@ namespace DotNetOpenAuth.OpenId {
/// Tracks the several versions of OpenID this library supports and the unique
/// constants to each version used in the protocol.
/// </summary>
+ [DebuggerDisplay("OpenID {Version}")]
internal class Protocol {
/// <summary>
/// The value of the openid.ns parameter in the OpenID 2.0 specification.
diff --git a/src/DotNetOpenAuth/Yadis/HtmlParser.cs b/src/DotNetOpenAuth/Yadis/HtmlParser.cs
index a6b64c1..406cb4b 100644
--- a/src/DotNetOpenAuth/Yadis/HtmlParser.cs
+++ b/src/DotNetOpenAuth/Yadis/HtmlParser.cs
@@ -98,8 +98,8 @@ namespace DotNetOpenAuth.Yadis {
/// <param name="attribute">The attribute.</param>
/// <returns>A filtered sequence of attributes.</returns>
internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl {
- Contract.Requires<ArgumentNullException>(sequence != null);
- Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(attribute));
+ Contract.Requires(sequence != null);
+ Contract.Requires(!String.IsNullOrEmpty(attribute));
return sequence.Where(tag => tag.Attributes[attribute] != null);
}