summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.Core')
-rw-r--r--src/DotNetOpenAuth.Core/Assumes.cs8
-rw-r--r--src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd89
-rw-r--r--src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj4
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Base64WebEncoder.cs37
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Channel.cs59
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/DataBag.cs4
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs15
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs16
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs15
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs22
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs75
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs220
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs18
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs8
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs69
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs2
-rw-r--r--src/DotNetOpenAuth.Core/Requires.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Strings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth.Core/Strings.resx10
25 files changed, 615 insertions, 102 deletions
diff --git a/src/DotNetOpenAuth.Core/Assumes.cs b/src/DotNetOpenAuth.Core/Assumes.cs
index f29f09f..151fa2f 100644
--- a/src/DotNetOpenAuth.Core/Assumes.cs
+++ b/src/DotNetOpenAuth.Core/Assumes.cs
@@ -58,6 +58,14 @@ namespace DotNetOpenAuth {
}
/// <summary>
+ /// Throws an internal error exception.
+ /// </summary>
+ /// <returns>Nothing. This method always throws.</returns>
+ internal static Exception NotReachable() {
+ throw new InternalErrorException();
+ }
+
+ /// <summary>
/// An internal error exception that should never be caught.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception should never be caught.")]
diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
index d193776..74d4db4 100644
--- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
@@ -479,12 +479,19 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="add">
<xs:complexType>
- <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the type that implements the IIdentifierDiscoveryService interface.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="xaml" type="xs:string" use="optional" />
</xs:complexType>
</xs:element>
<xs:element name="remove">
<xs:complexType>
- <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="clear">
@@ -898,6 +905,84 @@
</xs:choice>
</xs:complexType>
</xs:element>
+ <xs:element name="oauth2">
+ <xs:annotation>
+ <xs:documentation>
+ Settings OAuth 2 clients, authorization servers and resource servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="client">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Clients.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="authorizationServer">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Authorization Servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="clientAuthenticationModules">
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="add">
+ <xs:complexType>
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the ClientAuthenticationModule-derived type.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="xaml" type="xs:string" use="optional" />
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="remove">
+ <xs:complexType>
+ <xs:attribute name="type" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>
+ The fully-qualified name of the ClientAuthenticationModule-derived type.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="clear">
+ <xs:complexType>
+ <!--tag is empty-->
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="resourceServer">
+ <xs:annotation>
+ <xs:documentation>
+ Settings applicable to OAuth 2 Resource Servers.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
+ </xs:choice>
+ </xs:complexType>
+ </xs:element>
<xs:element name="reporting">
<xs:annotation>
<xs:documentation>
diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
index 447a3c5..5e079a0 100644
--- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
+++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
@@ -19,6 +19,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Assumes.cs" />
+ <Compile Include="Messaging\Base64WebEncoder.cs" />
<Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" />
<Compile Include="Messaging\Bindings\CryptoKey.cs" />
<Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" />
@@ -29,6 +30,8 @@
<Compile Include="Messaging\ChannelContract.cs" />
<Compile Include="Messaging\DataBagFormatterBase.cs" />
<Compile Include="Messaging\HttpRequestHeaders.cs" />
+ <Compile Include="Messaging\IHttpDirectRequest.cs" />
+ <Compile Include="Messaging\IHttpDirectRequestContract.cs" />
<Compile Include="Messaging\IHttpIndirectResponse.cs" />
<Compile Include="Messaging\IMessageOriginalPayload.cs" />
<Compile Include="Messaging\DirectWebRequestOptions.cs" />
@@ -53,6 +56,7 @@
<Compile Include="Messaging\MultipartPostPart.cs" />
<Compile Include="Messaging\NetworkDirectWebResponse.cs" />
<Compile Include="Messaging\OutgoingWebResponseActionResult.cs" />
+ <Compile Include="Messaging\ProtocolFaultResponseException.cs" />
<Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" />
<Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" />
<Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" />
diff --git a/src/DotNetOpenAuth.Core/Messaging/Base64WebEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Base64WebEncoder.cs
new file mode 100644
index 0000000..135e650
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/Base64WebEncoder.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="Base64WebEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// A message part encoder that translates between <c>byte[]</c> and base64web encoded strings.
+ /// </summary>
+ internal class Base64WebEncoder : IMessagePartEncoder {
+ /// <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) {
+ return MessagingUtilities.ConvertToBase64WebSafeString((byte[])value);
+ }
+
+ /// <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) {
+ return MessagingUtilities.FromBase64WebSafeString(value);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
index c58702c..672a942 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
@@ -38,6 +38,16 @@ namespace DotNetOpenAuth.Messaging {
internal static readonly Encoding PostEntityEncoding = new UTF8Encoding(false);
/// <summary>
+ /// A default set of XML dictionary reader quotas that are relatively safe from causing unbounded memory consumption.
+ /// </summary>
+ internal static readonly XmlDictionaryReaderQuotas DefaultUntrustedXmlDictionaryReaderQuotas = new XmlDictionaryReaderQuotas {
+ MaxArrayLength = 1,
+ MaxDepth = 2,
+ MaxBytesPerRead = 8 * 1024,
+ MaxStringContentLength = 16 * 1024,
+ };
+
+ /// <summary>
/// The content-type used on HTTP POST requests where the POST entity is a
/// URL-encoded series of key=value pairs.
/// </summary>
@@ -143,18 +153,16 @@ namespace DotNetOpenAuth.Messaging {
/// A class prepared to analyze incoming messages and indicate what concrete
/// message types can deserialize from it.
/// </param>
- /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param>
+ /// <param name="bindingElements">
+ /// The binding elements to use in sending and receiving messages.
+ /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.
+ /// </param>
protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) {
Requires.NotNull(messageTypeProvider, "messageTypeProvider");
this.messageTypeProvider = messageTypeProvider;
this.WebRequestHandler = new StandardWebRequestHandler();
- this.XmlDictionaryReaderQuotas = new XmlDictionaryReaderQuotas {
- MaxArrayLength = 1,
- MaxDepth = 2,
- MaxBytesPerRead = 8 * 1024,
- MaxStringContentLength = 16 * 1024,
- };
+ this.XmlDictionaryReaderQuotas = DefaultUntrustedXmlDictionaryReaderQuotas;
this.outgoingBindingElements = new List<IChannelBindingElement>(ValidateAndPrepareBindingElements(bindingElements));
this.incomingBindingElements = new List<IChannelBindingElement>(this.outgoingBindingElements);
@@ -475,6 +483,14 @@ namespace DotNetOpenAuth.Messaging {
IDirectedProtocolMessage requestMessage = this.ReadFromRequestCore(httpRequest);
if (requestMessage != null) {
Logger.Channel.DebugFormat("Incoming request received: {0}", requestMessage.GetType().Name);
+
+ var directRequest = requestMessage as IHttpDirectRequest;
+ if (directRequest != null) {
+ foreach (string header in httpRequest.Headers) {
+ directRequest.Headers[header] = httpRequest.Headers[header];
+ }
+ }
+
this.ProcessIncomingMessage(requestMessage);
}
@@ -714,6 +730,13 @@ namespace DotNetOpenAuth.Messaging {
Requires.True(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient);
HttpWebRequest webRequest = this.CreateHttpRequest(request);
+ var directRequest = request as IHttpDirectRequest;
+ if (directRequest != null) {
+ foreach (string header in directRequest.Headers) {
+ webRequest.Headers[header] = directRequest.Headers[header];
+ }
+ }
+
IDictionary<string, string> responseFields;
IDirectResponseProtocolMessage responseMessage;
@@ -973,17 +996,7 @@ namespace DotNetOpenAuth.Messaging {
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
protected virtual string SerializeAsJson(IMessage message) {
Requires.NotNull(message, "message");
-
- MessageDictionary messageDictionary = this.MessageDescriptions.GetAccessor(message);
- using (var memoryStream = new MemoryStream()) {
- using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, Encoding.UTF8)) {
- MessageSerializer.Serialize(messageDictionary, jsonWriter);
- jsonWriter.Flush();
- }
-
- string json = Encoding.UTF8.GetString(memoryStream.ToArray());
- return json;
- }
+ return MessagingUtilities.SerializeAsJson(message, this.MessageDescriptions);
}
/// <summary>
@@ -1079,6 +1092,7 @@ namespace DotNetOpenAuth.Messaging {
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, fields);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+ this.PrepareHttpWebRequest(httpRequest);
return httpRequest;
}
@@ -1119,6 +1133,7 @@ namespace DotNetOpenAuth.Messaging {
var fields = messageAccessor.Serialize();
var httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+ this.PrepareHttpWebRequest(httpRequest);
httpRequest.CachePolicy = this.CachePolicy;
httpRequest.Method = "POST";
@@ -1296,6 +1311,14 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Performs additional processing on an outgoing web request before it is sent to the remote server.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ protected virtual void PrepareHttpWebRequest(HttpWebRequest request) {
+ Requires.NotNull(request, "request");
+ }
+
+ /// <summary>
/// Customizes the binding element order for outgoing and incoming messages.
/// </summary>
/// <param name="outgoingOrder">The outgoing order.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
index c9c3415..0800840 100644
--- a/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/DataBag.cs
@@ -14,7 +14,7 @@ namespace DotNetOpenAuth.Messaging {
/// A collection of message parts that will be serialized into a single string,
/// to be set into a larger message.
/// </summary>
- internal abstract class DataBag : IMessage {
+ public abstract class DataBag : IMessage {
/// <summary>
/// The default version for DataBags.
/// </summary>
@@ -105,7 +105,7 @@ namespace DotNetOpenAuth.Messaging {
/// </remarks>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Accessed by reflection")]
[MessagePart("t", IsRequired = true, AllowEmpty = false)]
- private Type BagType {
+ protected virtual Type BagType {
get { return this.GetType(); }
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
index 9d4b93e..c9ceb81 100644
--- a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
@@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Messaging {
/// A serializer for <see cref="DataBag"/>-derived types
/// </summary>
/// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam>
- internal abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag, new() {
+ internal abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag {
/// <summary>
/// The message description cache to use for data bag types.
/// </summary>
@@ -146,6 +146,8 @@ namespace DotNetOpenAuth.Messaging {
/// <returns>A non-null, non-empty value.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
public string Serialize(T message) {
+ Requires.NotNull(message, "message");
+
message.UtcCreationDate = DateTime.UtcNow;
if (this.decodeOnceOnly != null) {
@@ -190,14 +192,13 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Deserializes a <see cref="DataBag"/>, including decompression, decryption, signature and nonce validation where applicable.
/// </summary>
+ /// <param name="message">The instance to initialize with deserialized data.</param>
/// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be null.</param>
/// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param>
/// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation.</param>
- /// <returns>
- /// The deserialized value. Never null.
- /// </returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
- public T Deserialize(IProtocolMessage containingMessage, string value, string messagePartName) {
+ public void Deserialize(T message, IProtocolMessage containingMessage, string value, string messagePartName) {
+ Requires.NotNull(message, "message");
Requires.NotNull(containingMessage, "containingMessage");
Requires.NotNullOrEmpty(value, "value");
Requires.NotNullOrEmpty(messagePartName, "messagePartName");
@@ -209,7 +210,7 @@ namespace DotNetOpenAuth.Messaging {
value = valueWithoutHandle;
}
- var message = new T { ContainingMessage = containingMessage };
+ message.ContainingMessage = containingMessage;
byte[] data = MessagingUtilities.FromBase64WebSafeString(value);
byte[] signature = null;
@@ -254,8 +255,6 @@ namespace DotNetOpenAuth.Messaging {
}
((IMessage)message).EnsureValidMessage();
-
- return message;
}
/// <summary>
diff --git a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
index f499d67..2237cc7 100644
--- a/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/ErrorUtilities.cs
@@ -193,17 +193,17 @@ namespace DotNetOpenAuth.Messaging {
/// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false.
/// </summary>
/// <param name="condition">True to do nothing; false to throw the exception.</param>
- /// <param name="message">The error message for the exception.</param>
+ /// <param name="unformattedMessage">The error message for the exception.</param>
/// <param name="args">The string formatting arguments, if any.</param>
/// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception>
[Pure]
- internal static void VerifyProtocol(bool condition, string message, params object[] args) {
+ internal static void VerifyProtocol(bool condition, string unformattedMessage, params object[] args) {
Requires.NotNull(args, "args");
Contract.Ensures(condition);
Contract.EnsuresOnThrow<ProtocolException>(!condition);
- Contract.Assume(message != null);
+ Contract.Assume(unformattedMessage != null);
if (!condition) {
- var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args));
+ var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, unformattedMessage, args));
if (Logger.Messaging.IsErrorEnabled) {
Logger.Messaging.Error(
string.Format(
@@ -220,7 +220,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Throws a <see cref="ProtocolException"/>.
/// </summary>
- /// <param name="message">The message to set in the exception.</param>
+ /// <param name="unformattedMessage">The message to set in the exception.</param>
/// <param name="args">The formatting arguments of the message.</param>
/// <returns>
/// An InternalErrorException, which may be "thrown" by the caller in order
@@ -229,10 +229,10 @@ namespace DotNetOpenAuth.Messaging {
/// </returns>
/// <exception cref="ProtocolException">Always thrown.</exception>
[Pure]
- internal static Exception ThrowProtocol(string message, params object[] args) {
+ internal static Exception ThrowProtocol(string unformattedMessage, params object[] args) {
Requires.NotNull(args, "args");
- Contract.Assume(message != null);
- VerifyProtocol(false, message, args);
+ Contract.Assume(unformattedMessage != null);
+ VerifyProtocol(false, unformattedMessage, args);
// we never reach here, but this allows callers to "throw" this method.
return new InternalErrorException();
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
index 9579a81..dad6bf6 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestHeaders.cs
@@ -20,6 +20,11 @@ namespace DotNetOpenAuth.Messaging {
internal const string Authorization = "Authorization";
/// <summary>
+ /// The WWW-Authenticate header, which is included in HTTP 401 Unauthorized responses to help the client know which authorization schemes are supported.
+ /// </summary>
+ internal const string WwwAuthenticate = "WWW-Authenticate";
+
+ /// <summary>
/// The Content-Type header, which specifies the MIME type of the accompanying body data.
/// </summary>
internal const string ContentType = "Content-Type";
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
index 0f60e04..f613dc5 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
@@ -90,7 +90,7 @@ namespace DotNetOpenAuth.Messaging {
this.requestUri = requestUri;
this.form = form ?? new NameValueCollection();
this.queryString = HttpUtility.ParseQueryString(requestUri.Query);
- this.headers = headers ?? new NameValueCollection();
+ this.headers = headers ?? new WebHeaderCollection();
this.serverVariables = new NameValueCollection();
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
index 9086ee9..923773e 100644
--- a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
@@ -13,7 +13,7 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam>
[ContractClass(typeof(IDataBagFormatterContract<>))]
- internal interface IDataBagFormatter<T> where T : DataBag, new() {
+ internal interface IDataBagFormatter<in T> where T : DataBag {
/// <summary>
/// Serializes the specified message.
/// </summary>
@@ -24,13 +24,11 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Deserializes a <see cref="DataBag"/>.
/// </summary>
+ /// <param name="message">The instance to deserialize into</param>
/// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be null.</param>
/// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param>
/// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation.</param>
- /// <returns>
- /// The deserialized value. Never null.
- /// </returns>
- T Deserialize(IProtocolMessage containingMessage, string data, string messagePartName);
+ void Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName);
}
/// <summary>
@@ -62,13 +60,12 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Deserializes a <see cref="DataBag"/>.
/// </summary>
+ /// <param name="message">The instance to deserialize into</param>
/// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param>
/// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param>
/// <param name="messagePartName">Name of the message part whose value is to be deserialized. Used for exception messages.</param>
- /// <returns>
- /// The deserialized value. Never null.
- /// </returns>
- T IDataBagFormatter<T>.Deserialize(IProtocolMessage containingMessage, string data, string messagePartName) {
+ void IDataBagFormatter<T>.Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName) {
+ Requires.NotNull(message, "message");
Requires.NotNull(containingMessage, "containingMessage");
Requires.NotNullOrEmpty(data, "data");
Requires.NotNullOrEmpty(messagePartName, "messagePartName");
diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs
new file mode 100644
index 0000000..7153334
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequest.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="IHttpDirectRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System.Diagnostics.Contracts;
+ using System.Net;
+
+ /// <summary>
+ /// An interface that allows direct request messages to capture the details of the HTTP request they arrived on.
+ /// </summary>
+ [ContractClass(typeof(IHttpDirectRequestContract))]
+ public interface IHttpDirectRequest : IMessage {
+ /// <summary>
+ /// Gets the HTTP headers of the request.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ WebHeaderCollection Headers { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs
new file mode 100644
index 0000000..cfde6cf
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/IHttpDirectRequestContract.cs
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------
+// <copyright file="IHttpDirectRequestContract.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+
+ /// <summary>
+ /// Contract class for the <see cref="IHttpDirectRequest"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IHttpDirectRequest))]
+ public abstract class IHttpDirectRequestContract : IHttpDirectRequest {
+ #region IHttpDirectRequest Members
+
+ /// <summary>
+ /// Gets the HTTP headers of the request.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ WebHeaderCollection IHttpDirectRequest.Headers {
+ get {
+ Contract.Ensures(Contract.Result<WebHeaderCollection>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IMessage Members
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ Version IMessage.Version {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ IDictionary<string, string> IMessage.ExtraData {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void IMessage.EnsureValidMessage() {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
index bdca190..15df48a 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
@@ -74,7 +74,7 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Reads the data from a message instance and writes a XML/JSON encoding of it.
+ /// Reads the data from a message instance and writes an XML/JSON encoding of it.
/// </summary>
/// <param name="messageDictionary">The message to be serialized.</param>
/// <param name="writer">The writer to use for the serialized form.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
index b26deeb..e821953 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
@@ -16,11 +16,13 @@ namespace DotNetOpenAuth.Messaging {
using System.Linq;
using System.Net;
using System.Net.Mime;
+ using System.Runtime.Serialization.Json;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Mvc;
+ using System.Xml;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.Messaging.Reflection;
@@ -135,6 +137,21 @@ namespace DotNetOpenAuth.Messaging {
};
/// <summary>
+ /// The available compression algorithms.
+ /// </summary>
+ internal enum CompressionMethod {
+ /// <summary>
+ /// The Deflate algorithm.
+ /// </summary>
+ Deflate,
+
+ /// <summary>
+ /// The GZip algorithm.
+ /// </summary>
+ Gzip,
+ }
+
+ /// <summary>
/// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
/// </summary>
/// <param name="response">The response to send to the user agent.</param>
@@ -290,6 +307,56 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Compares to string values for ordinal equality in such a way that its execution time does not depend on how much of the value matches.
+ /// </summary>
+ /// <param name="value1">The first value.</param>
+ /// <param name="value2">The second value.</param>
+ /// <returns>A value indicating whether the two strings share ordinal equality.</returns>
+ /// <remarks>
+ /// In signature equality checks, a difference in execution time based on how many initial characters match MAY
+ /// be used as an attack to figure out the expected signature. It is therefore important to make a signature
+ /// equality check's execution time independent of how many characters match the expected value.
+ /// See http://codahale.com/a-lesson-in-timing-attacks/ for more information.
+ /// </remarks>
+ public static bool EqualsConstantTime(string value1, string value2) {
+ // If exactly one value is null, they don't match.
+ if (value1 == null ^ value2 == null) {
+ return false;
+ }
+
+ // If both values are null (since if one is at this point then they both are), it's a match.
+ if (value1 == null) {
+ return true;
+ }
+
+ if (value1.Length != value2.Length) {
+ return false;
+ }
+
+ // This looks like a pretty crazy way to compare values, but it provides a constant time equality check,
+ // and is more resistant to compiler optimizations than simply setting a boolean flag and returning the boolean after the loop.
+ int result = 0;
+ for (int i = 0; i < value1.Length; i++) {
+ result |= value1[i] ^ value2[i];
+ }
+
+ return result == 0;
+ }
+
+ /// <summary>
+ /// Gets the URL to the root of a web site, which may include a virtual directory path.
+ /// </summary>
+ /// <returns>An absolute URI.</returns>
+ internal static Uri GetWebRoot() {
+ HttpRequestBase requestInfo = new HttpRequestWrapper(HttpContext.Current.Request);
+ UriBuilder realmUrl = new UriBuilder(requestInfo.GetPublicFacingUrl());
+ realmUrl.Path = HttpContext.Current.Request.ApplicationPath;
+ realmUrl.Query = null;
+ realmUrl.Fragment = null;
+ return realmUrl.Uri;
+ }
+
+ /// <summary>
/// Clears any existing elements in a collection and fills the collection with a given set of values.
/// </summary>
/// <typeparam name="T">The type of value kept in the collection.</typeparam>
@@ -756,6 +823,12 @@ namespace DotNetOpenAuth.Messaging {
var cryptoKeyPair = cryptoKeyStore.GetKeys(bucket).FirstOrDefault(pair => pair.Value.Key.Length == keySize / 8);
if (cryptoKeyPair.Value == null || cryptoKeyPair.Value.ExpiresUtc < DateTime.UtcNow + minimumRemainingLife) {
// No key exists with enough remaining life for the required purpose. Create a new key.
+ if (cryptoKeyPair.Value == null) {
+ Logger.Messaging.InfoFormat("{0}.GetKeys returned no keys for bucket \"{1}\" with the required key length of {2} bits. A new key will be created", typeof(ICryptoKeyStore), bucket, keySize);
+ } else {
+ Logger.Messaging.InfoFormat("The first key returned by {0}.GetKeys for bucket \"{1}\" with the required key length of {2} bits was too near expiry to use. A new key will be created", typeof(ICryptoKeyStore), bucket, keySize);
+ }
+
ErrorUtilities.VerifyHost(minimumRemainingLife <= SymmetricSecretKeyLifespan, "Unable to create a new symmetric key with the required lifespan of {0} because it is beyond the limit of {1}.", minimumRemainingLife, SymmetricSecretKeyLifespan);
byte[] secret = GetCryptoRandomData(keySize / 8);
DateTime expires = DateTime.UtcNow + SymmetricSecretKeyLifespan;
@@ -770,6 +843,7 @@ namespace DotNetOpenAuth.Messaging {
cryptoKeyStore.StoreKey(bucket, handle, cryptoKey);
} catch (CryptoKeyCollisionException) {
ErrorUtilities.VerifyInternal(++failedAttempts < 3, "Unable to derive a unique handle to a private symmetric key.");
+ Logger.Messaging.Warn("A randomly generated crypto key handle collided with an existing handle. Another randomly generated handle will be attempted till the retry count is met.");
goto tryAgain;
}
}
@@ -781,19 +855,36 @@ namespace DotNetOpenAuth.Messaging {
/// Compresses a given buffer.
/// </summary>
/// <param name="buffer">The buffer to compress.</param>
+ /// <param name="method">The compression algorithm to use.</param>
/// <returns>The compressed data.</returns>
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")]
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
- internal static byte[] Compress(byte[] buffer) {
+ internal static byte[] Compress(byte[] buffer, CompressionMethod method = CompressionMethod.Deflate) {
Requires.NotNull(buffer, "buffer");
Contract.Ensures(Contract.Result<byte[]>() != null);
using (var ms = new MemoryStream()) {
- using (var compressingStream = new DeflateStream(ms, CompressionMode.Compress, true)) {
+ Stream compressingStream = null;
+ try {
+ switch (method) {
+ case CompressionMethod.Deflate:
+ compressingStream = new DeflateStream(ms, CompressionMode.Compress, true);
+ break;
+ case CompressionMethod.Gzip:
+ compressingStream = new GZipStream(ms, CompressionMode.Compress, true);
+ break;
+ default:
+ Requires.InRange(false, "method");
+ break;
+ }
+
compressingStream.Write(buffer, 0, buffer.Length);
+ return ms.ToArray();
+ } finally {
+ if (compressingStream != null) {
+ compressingStream.Dispose();
+ }
}
-
- return ms.ToArray();
}
}
@@ -801,17 +892,35 @@ namespace DotNetOpenAuth.Messaging {
/// Decompresses a given buffer.
/// </summary>
/// <param name="buffer">The buffer to decompress.</param>
+ /// <param name="method">The compression algorithm used.</param>
/// <returns>The decompressed data.</returns>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")]
- internal static byte[] Decompress(byte[] buffer) {
+ internal static byte[] Decompress(byte[] buffer, CompressionMethod method = CompressionMethod.Deflate) {
Requires.NotNull(buffer, "buffer");
Contract.Ensures(Contract.Result<byte[]>() != null);
using (var compressedDataStream = new MemoryStream(buffer)) {
using (var decompressedDataStream = new MemoryStream()) {
- using (var decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true)) {
+ Stream decompressingStream = null;
+ try {
+ switch (method) {
+ case CompressionMethod.Deflate:
+ decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true);
+ break;
+ case CompressionMethod.Gzip:
+ decompressingStream = new GZipStream(compressedDataStream, CompressionMode.Decompress, true);
+ break;
+ default:
+ Requires.InRange(false, "method");
+ break;
+ }
+
decompressingStream.CopyTo(decompressedDataStream);
+ } finally {
+ if (decompressingStream != null) {
+ decompressingStream.Dispose();
+ }
}
return decompressedDataStream.ToArray();
@@ -868,43 +977,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Compares to string values for ordinal equality in such a way that its execution time does not depend on how much of the value matches.
- /// </summary>
- /// <param name="value1">The first value.</param>
- /// <param name="value2">The second value.</param>
- /// <returns>A value indicating whether the two strings share ordinal equality.</returns>
- /// <remarks>
- /// In signature equality checks, a difference in execution time based on how many initial characters match MAY
- /// be used as an attack to figure out the expected signature. It is therefore important to make a signature
- /// equality check's execution time independent of how many characters match the expected value.
- /// See http://codahale.com/a-lesson-in-timing-attacks/ for more information.
- /// </remarks>
- internal static bool EqualsConstantTime(string value1, string value2) {
- // If exactly one value is null, they don't match.
- if (value1 == null ^ value2 == null) {
- return false;
- }
-
- // If both values are null (since if one is at this point then they both are), it's a match.
- if (value1 == null) {
- return true;
- }
-
- if (value1.Length != value2.Length) {
- return false;
- }
-
- // This looks like a pretty crazy way to compare values, but it provides a constant time equality check,
- // and is more resistant to compiler optimizations than simply setting a boolean flag and returning the boolean after the loop.
- int result = 0;
- for (int i = 0; i < value1.Length; i++) {
- result |= value1[i] ^ value2[i];
- }
-
- return result == 0;
- }
-
- /// <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" />
@@ -1600,6 +1672,68 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Serializes the given message as a JSON string.
+ /// </summary>
+ /// <param name="message">The message to serialize.</param>
+ /// <param name="messageDescriptions">The cached message descriptions to use for reflection.</param>
+ /// <returns>A JSON string.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
+ internal static string SerializeAsJson(IMessage message, MessageDescriptionCollection messageDescriptions) {
+ Requires.NotNull(message, "message");
+ Requires.NotNull(messageDescriptions, "messageDescriptions");
+
+ var encoding = Encoding.UTF8;
+ var bytes = SerializeAsJsonBytes(message, messageDescriptions, encoding);
+ string json = encoding.GetString(bytes);
+ return json;
+ }
+
+ /// <summary>
+ /// Serializes the given message as a JSON string.
+ /// </summary>
+ /// <param name="message">The message to serialize.</param>
+ /// <param name="messageDescriptions">The cached message descriptions to use for reflection.</param>
+ /// <param name="encoding">The encoding to use. Defaults to <see cref="Encoding.UTF8"/></param>
+ /// <returns>A JSON string.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")]
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
+ internal static byte[] SerializeAsJsonBytes(IMessage message, MessageDescriptionCollection messageDescriptions, Encoding encoding = null) {
+ Requires.NotNull(message, "message");
+ Requires.NotNull(messageDescriptions, "messageDescriptions");
+
+ encoding = encoding ?? Encoding.UTF8;
+ MessageDictionary messageDictionary = messageDescriptions.GetAccessor(message);
+ using (var memoryStream = new MemoryStream()) {
+ using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, encoding)) {
+ MessageSerializer.Serialize(messageDictionary, jsonWriter);
+ jsonWriter.Flush();
+ }
+
+ return memoryStream.ToArray();
+ }
+ }
+
+ /// <summary>
+ /// Deserializes a JSON object into a message.
+ /// </summary>
+ /// <param name="jsonBytes">The buffer containing the JSON string.</param>
+ /// <param name="receivingMessage">The message to deserialize the object into.</param>
+ /// <param name="messageDescriptions">The cache of message descriptions.</param>
+ /// <param name="encoding">The encoding that the JSON bytes are in.</param>
+ internal static void DeserializeFromJson(byte[] jsonBytes, IMessage receivingMessage, MessageDescriptionCollection messageDescriptions, Encoding encoding = null) {
+ Requires.NotNull(jsonBytes, "jsonBytes");
+ Requires.NotNull(receivingMessage, "receivingMessage");
+ Requires.NotNull(messageDescriptions, "messageDescriptions");
+
+ encoding = encoding ?? Encoding.UTF8;
+ MessageDictionary messageDictionary = messageDescriptions.GetAccessor(receivingMessage);
+ using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(jsonBytes, 0, jsonBytes.Length, encoding, Channel.DefaultUntrustedXmlDictionaryReaderQuotas, null)) {
+ MessageSerializer.Deserialize(messageDictionary, jsonReader);
+ }
+ }
+
+ /// <summary>
/// Prepares what SHOULD be simply a string value for safe injection into Javascript
/// by using appropriate character escaping.
/// </summary>
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
index 67eccce..9ef89e9 100644
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
@@ -12,6 +12,7 @@ namespace DotNetOpenAuth.Messaging {
using System.IO;
using System.Net;
using System.Net.Mime;
+ using System.ServiceModel.Web;
using System.Text;
using System.Threading;
using System.Web;
@@ -213,6 +214,23 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Submits this response to a WCF response context. Only available when no response body is included.
+ /// </summary>
+ /// <param name="responseContext">The response context to apply the response to.</param>
+ public virtual void Respond(OutgoingWebResponseContext responseContext) {
+ Requires.NotNull(responseContext, "responseContext");
+ if (this.ResponseStream != null) {
+ throw new NotSupportedException(Strings.ResponseBodyNotSupported);
+ }
+
+ responseContext.StatusCode = this.Status;
+ responseContext.SuppressEntityBody = true;
+ foreach (string header in this.Headers) {
+ responseContext.Headers[header] = this.Headers[header];
+ }
+ }
+
+ /// <summary>
/// Automatically sends the appropriate response to the user agent.
/// </summary>
/// <param name="response">The response to set to this message.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
index a5fe782..7691cc4 100644
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponseActionResult.cs
@@ -35,6 +35,11 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="context">The context in which to set the response.</param>
public override void ExecuteResult(ControllerContext context) {
this.response.Respond(context.HttpContext);
+
+ // MVC likes to muck with our response. For example, when returning contrived 401 Unauthorized responses
+ // MVC will rewrite our response and turn it into a redirect, which breaks OAuth 2 authorization server token endpoints.
+ // It turns out we can prevent this unwanted behavior by flushing the response before returning from this method.
+ context.HttpContext.Response.Flush();
}
}
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs
index e26d15e..982e1c0 100644
--- a/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolException.cs
@@ -42,10 +42,10 @@ namespace DotNetOpenAuth.Messaging {
/// such that it can be sent as a protocol message response to a remote caller.
/// </summary>
/// <param name="message">The human-readable exception message.</param>
- /// <param name="faultedMessage">The message that was the cause of the exception. Must not be null.</param>
- protected internal ProtocolException(string message, IProtocolMessage faultedMessage)
- : base(message) {
- Requires.NotNull(faultedMessage, "faultedMessage");
+ /// <param name="faultedMessage">The message that was the cause of the exception. May be null.</param>
+ /// <param name="innerException">The inner exception to include.</param>
+ protected internal ProtocolException(string message, IProtocolMessage faultedMessage, Exception innerException = null)
+ : base(message, innerException) {
this.FaultedMessage = faultedMessage;
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs
new file mode 100644
index 0000000..c2dc34e
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/ProtocolFaultResponseException.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProtocolFaultResponseException.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// An exception to represent errors in the local or remote implementation of the protocol
+ /// that includes the response message that should be returned to the HTTP client to comply
+ /// with the protocol specification.
+ /// </summary>
+ public class ProtocolFaultResponseException : ProtocolException {
+ /// <summary>
+ /// The channel that produced the error response message, to be used in constructing the actual HTTP response.
+ /// </summary>
+ private readonly Channel channel;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolFaultResponseException"/> class
+ /// such that it can be sent as a protocol message response to a remote caller.
+ /// </summary>
+ /// <param name="channel">The channel to use when encoding the response message.</param>
+ /// <param name="errorResponse">The message to send back to the HTTP client.</param>
+ /// <param name="faultedMessage">The message that was the cause of the exception. May be null.</param>
+ /// <param name="innerException">The inner exception.</param>
+ /// <param name="message">The message for the exception.</param>
+ protected internal ProtocolFaultResponseException(Channel channel, IDirectResponseProtocolMessage errorResponse, IProtocolMessage faultedMessage = null, Exception innerException = null, string message = null)
+ : base(message ?? (innerException != null ? innerException.Message : null), faultedMessage, innerException) {
+ Requires.NotNull(channel, "channel");
+ Requires.NotNull(errorResponse, "errorResponse");
+ this.channel = channel;
+ this.ErrorResponseMessage = errorResponse;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolFaultResponseException"/> class.
+ /// </summary>
+ /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/>
+ /// that holds the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The System.Runtime.Serialization.StreamingContext
+ /// that contains contextual information about the source or destination.</param>
+ protected ProtocolFaultResponseException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context)
+ : base(info, context) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets the protocol message to send back to the client to report the error.
+ /// </summary>
+ public IDirectResponseProtocolMessage ErrorResponseMessage { get; private set; }
+
+ /// <summary>
+ /// Creates the HTTP response to forward to the client to report the error.
+ /// </summary>
+ /// <returns>The HTTP response.</returns>
+ public OutgoingWebResponse CreateErrorResponse() {
+ var response = this.channel.PrepareResponse(this.ErrorResponseMessage);
+ return response;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs
index 7a1d194..7ca5d45 100644
--- a/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/StandardMessageFactoryChannel.cs
@@ -31,7 +31,10 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="messageTypes">The message types that might be encountered.</param>
/// <param name="versions">All the possible message versions that might be encountered.</param>
- /// <param name="bindingElements">The binding elements to apply to the channel.</param>
+ /// <param name="bindingElements">
+ /// The binding elements to use in sending and receiving messages.
+ /// The order they are provided is used for outgoing messgaes, and reversed for incoming messages.
+ /// </param>
protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements)
: base(new StandardMessageFactory(), bindingElements) {
Requires.NotNull(messageTypes, "messageTypes");
diff --git a/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs
index 92b1928..242175e 100644
--- a/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/UriStyleMessageFormatter.cs
@@ -20,7 +20,7 @@ namespace DotNetOpenAuth.Messaging {
/// A serializer for <see cref="DataBag"/>-derived types
/// </summary>
/// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam>
- internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag, new() {
+ internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag {
/// <summary>
/// Initializes a new instance of the <see cref="UriStyleMessageFormatter&lt;T&gt;"/> class.
/// </summary>
diff --git a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
index e57b211..91d27f5 100644
--- a/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
+++ b/src/DotNetOpenAuth.Core/Properties/AssemblyInfo.cs
@@ -64,6 +64,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdOAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ClientAuthorization, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
@@ -88,6 +89,7 @@ using System.Web.UI;
[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenIdOAuth")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ClientAuthorization")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")]
[assembly: InternalsVisibleTo("DotNetOpenAuth.AspNet.Test")]
diff --git a/src/DotNetOpenAuth.Core/Requires.cs b/src/DotNetOpenAuth.Core/Requires.cs
index 7a196a3..7d4d5be 100644
--- a/src/DotNetOpenAuth.Core/Requires.cs
+++ b/src/DotNetOpenAuth.Core/Requires.cs
@@ -43,14 +43,17 @@ namespace DotNetOpenAuth {
/// </summary>
/// <param name="value">The value.</param>
/// <param name="parameterName">Name of the parameter.</param>
+ /// <returns>The validated value.</returns>
#if !CLR4
[ContractArgumentValidator]
#endif
[Pure, DebuggerStepThrough]
- internal static void NotNullOrEmpty(string value, string parameterName) {
+ internal static string NotNullOrEmpty(string value, string parameterName) {
NotNull(value, parameterName);
True(value.Length > 0, parameterName, Strings.EmptyStringNotAllowed);
+ Contract.Ensures(Contract.Result<string>() == value);
Contract.EndContractBlock();
+ return value;
}
/// <summary>
diff --git a/src/DotNetOpenAuth.Core/Strings.Designer.cs b/src/DotNetOpenAuth.Core/Strings.Designer.cs
index 21411a1..b0e66d2 100644
--- a/src/DotNetOpenAuth.Core/Strings.Designer.cs
+++ b/src/DotNetOpenAuth.Core/Strings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30319.17291
+// Runtime Version:4.0.30319.17622
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -106,6 +106,24 @@ namespace DotNetOpenAuth {
}
/// <summary>
+ /// Looks up a localized string similar to The property {0} must be set before this operation is allowed..
+ /// </summary>
+ internal static string RequiredPropertyNotYetPreset {
+ get {
+ return ResourceManager.GetString("RequiredPropertyNotYetPreset", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This object contains a response body, which is not supported..
+ /// </summary>
+ internal static string ResponseBodyNotSupported {
+ get {
+ return ResourceManager.GetString("ResponseBodyNotSupported", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}..
/// </summary>
internal static string StoreRequiredWhenNoHttpContextAvailable {
diff --git a/src/DotNetOpenAuth.Core/Strings.resx b/src/DotNetOpenAuth.Core/Strings.resx
index 1c69ef7..f4d61d1 100644
--- a/src/DotNetOpenAuth.Core/Strings.resx
+++ b/src/DotNetOpenAuth.Core/Strings.resx
@@ -112,10 +112,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConfigurationTypeMustBePublic" xml:space="preserve">
<value>The configuration-specified type {0} must be public, and is not.</value>
@@ -135,4 +135,10 @@
<data name="InvalidArgument" xml:space="preserve">
<value>The argument has an unexpected value.</value>
</data>
+ <data name="RequiredPropertyNotYetPreset" xml:space="preserve">
+ <value>The property {0} must be set before this operation is allowed.</value>
+ </data>
+ <data name="ResponseBodyNotSupported" xml:space="preserve">
+ <value>This object contains a response body, which is not supported.</value>
+ </data>
</root> \ No newline at end of file