diff options
Diffstat (limited to 'src')
162 files changed, 4665 insertions, 2030 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<T>"/> 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 diff --git a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs index c424916..e50cafd 100644 --- a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs @@ -97,6 +97,10 @@ namespace DotNetOpenAuth.InfoCard { }), MaximumClockSkew); + if (audience != null) { + samlAuthenticator.AllowedAudienceUris.Add(audience.AbsoluteUri); + } + return AuthorizationContext.CreateDefaultAuthorizationContext(samlAuthenticator.ValidateToken(token)); } diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs index b04c67e..db131a9 100644 --- a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -297,6 +297,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData); } httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri); + this.PrepareHttpWebRequest(httpRequest); httpRequest.Method = GetHttpMethod(requestMessage); httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields)); diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs new file mode 100644 index 0000000..6511a11 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/Configuration/OAuth2AuthorizationServerSection.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2AuthorizationServerSection.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Configuration; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// Represents the <oauth2/authorizationServer> section in the host's .config file. + /// </summary> + internal class OAuth2AuthorizationServerSection : ConfigurationSection { + /// <summary> + /// The name of the oauth2/authorizationServer section. + /// </summary> + private const string SectionName = OAuth2SectionGroup.SectionName + "/authorizationServer"; + + /// <summary> + /// The name of the <clientAuthenticationModules> sub-element. + /// </summary> + private const string ClientAuthenticationModulesElementName = "clientAuthenticationModules"; + + /// <summary> + /// The built-in set of client authentication modules. + /// </summary> + private static readonly TypeConfigurationCollection<ClientAuthenticationModule> defaultClientAuthenticationModules = + new TypeConfigurationCollection<ClientAuthenticationModule>(new Type[] { typeof(ClientCredentialHttpBasicReader), typeof(ClientCredentialMessagePartReader) }); + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerSection"/> class. + /// </summary> + internal OAuth2AuthorizationServerSection() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + internal static OAuth2AuthorizationServerSection Configuration { + get { + Contract.Ensures(Contract.Result<OAuth2AuthorizationServerSection>() != null); + return (OAuth2AuthorizationServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2AuthorizationServerSection(); + } + } + + /// <summary> + /// Gets or sets the services to use for discovering service endpoints for identifiers. + /// </summary> + /// <remarks> + /// If no discovery services are defined in the (web) application's .config file, + /// the default set of discovery services built into the library are used. + /// </remarks> + [ConfigurationProperty(ClientAuthenticationModulesElementName, IsDefaultCollection = false)] + [ConfigurationCollection(typeof(TypeConfigurationCollection<ClientAuthenticationModule>))] + internal TypeConfigurationCollection<ClientAuthenticationModule> ClientAuthenticationModules { + get { + var configResult = (TypeConfigurationCollection<ClientAuthenticationModule>)this[ClientAuthenticationModulesElementName]; + return configResult != null && configResult.Count > 0 ? configResult : defaultClientAuthenticationModules; + } + + set { + this[ClientAuthenticationModulesElementName] = value; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj index f6b1a50..34d59ee 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj @@ -18,7 +18,33 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2AuthorizationServerSection.cs" /> <Compile Include="OAuth2\AuthorizationServer.cs" /> + <Compile Include="OAuth2\AuthorizationServerAccessToken.cs" /> + <Compile Include="OAuth2\AuthServerStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>AuthServerStrings.resx</DependentUpon> + </Compile> + <Compile Include="OAuth2\AuthServerUtilities.cs" /> + <Compile Include="OAuth2\ChannelElements\AggregatingClientCredentialReader.cs" /> + <Compile Include="OAuth2\ChannelElements\ClientCredentialHttpBasicReader.cs" /> + <Compile Include="OAuth2\ChannelElements\ClientCredentialMessagePartReader.cs" /> + <Compile Include="OAuth2\ChannelElements\TokenCodeSerializationBindingElement.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" /> + <Compile Include="OAuth2\ChannelElements\MessageValidationBindingElement.cs" /> + <Compile Include="OAuth2\ChannelElements\AuthServerBindingElementBase.cs" /> + <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithAuthorizationServer.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" /> + <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" /> + <Compile Include="OAuth2\ChannelElements\ClientAuthenticationModule.cs" /> + <Compile Include="OAuth2\ClientDescription.cs" /> + <Compile Include="OAuth2\IAuthorizationServerHost.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestAS.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRefreshRequestAS.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponseAS.cs" /> + <Compile Include="OAuth2\Messages\IAuthorizationCodeCarryingRequest.cs" /> + <Compile Include="OAuth2\Messages\IRefreshTokenCarryingRequest.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <ItemGroup> @@ -26,11 +52,21 @@ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> <Name>DotNetOpenAuth.Core</Name> </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj"> + <Project>{CCF3728A-B3D7-404A-9BC6-75197135F2D7}</Project> + <Name>DotNetOpenAuth.OAuth2.ClientAuthorization</Name> + </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> <Name>DotNetOpenAuth.OAuth2</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth2\AuthServerStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>AuthServerStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs new file mode 100644 index 0000000..4b4f830 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.17614 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OAuth2 { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class AuthServerStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal AuthServerStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth2.AuthServerStrings", typeof(AuthServerStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to The requested access scope exceeds the grant scope.. + /// </summary> + internal static string AccessScopeExceedsGrantScope { + get { + return ResourceManager.GetString("AccessScopeExceedsGrantScope", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The callback URL ({0}) is not allowed for this client.. + /// </summary> + internal static string ClientCallbackDisallowed { + get { + return ResourceManager.GetString("ClientCallbackDisallowed", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Failure looking up secret for client or token.. + /// </summary> + internal static string ClientOrTokenSecretNotFound { + get { + return ResourceManager.GetString("ClientOrTokenSecretNotFound", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The client secret was incorrect.. + /// </summary> + internal static string ClientSecretMismatch { + get { + return ResourceManager.GetString("ClientSecretMismatch", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Invalid resource owner password credential.. + /// </summary> + internal static string InvalidResourceOwnerPasswordCredential { + get { + return ResourceManager.GetString("InvalidResourceOwnerPasswordCredential", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No callback URI was available for this request.. + /// </summary> + internal static string NoCallback { + get { + return ResourceManager.GetString("NoCallback", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx new file mode 100644 index 0000000..29d841a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerStrings.resx @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="AccessScopeExceedsGrantScope" xml:space="preserve"> + <value>The requested access scope exceeds the grant scope.</value> + </data> + <data name="ClientCallbackDisallowed" xml:space="preserve"> + <value>The callback URL ({0}) is not allowed for this client.</value> + </data> + <data name="ClientOrTokenSecretNotFound" xml:space="preserve"> + <value>Failure looking up secret for client or token.</value> + </data> + <data name="ClientSecretMismatch" xml:space="preserve"> + <value>The client secret was incorrect.</value> + </data> + <data name="InvalidResourceOwnerPasswordCredential" xml:space="preserve"> + <value>Invalid resource owner password credential.</value> + </data> + <data name="NoCallback" xml:space="preserve"> + <value>No callback URI was available for this request.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs new file mode 100644 index 0000000..b8a1071 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthServerUtilities.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthServerUtilities.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Utility methods for authorization servers. + /// </summary> + internal static class AuthServerUtilities { + /// <summary> + /// Gets information about the client with a given identifier. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <returns>The client information. Never null.</returns> + internal static IClientDescription GetClientOrThrow(this IAuthorizationServerHost authorizationServer, string clientIdentifier) { + Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); + Contract.Ensures(Contract.Result<IClientDescription>() != null); + + try { + var result = authorizationServer.GetClient(clientIdentifier); + ErrorUtilities.VerifyHost(result != null, OAuthStrings.ResultShouldNotBeNull, authorizationServer.GetType().FullName, "GetClient(string)"); + return result; + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, AuthServerStrings.ClientOrTokenSecretNotFound); + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, AuthServerStrings.ClientOrTokenSecretNotFound); + } + } + + /// <summary> + /// Verifies a condition is true or throws an exception describing the problem. + /// </summary> + /// <param name="condition">The condition that evaluates to true to avoid an exception.</param> + /// <param name="requestMessage">The request message.</param> + /// <param name="error">A single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>.</param> + /// <param name="authenticationModule">The authentication module from which to glean the WWW-Authenticate header when applicable.</param> + /// <param name="unformattedDescription">A human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred.</param> + /// <param name="args">The formatting arguments to generate the actual description.</param> + internal static void TokenEndpointVerify(bool condition, AccessTokenRequestBase requestMessage, string error, ClientAuthenticationModule authenticationModule = null, string unformattedDescription = null, params object[] args) { + if (!condition) { + string description = unformattedDescription != null ? string.Format(CultureInfo.CurrentCulture, unformattedDescription, args) : null; + + string wwwAuthenticateHeader = null; + if (authenticationModule != null) { + wwwAuthenticateHeader = authenticationModule.AuthenticateHeader; + } + + throw new TokenEndpointProtocolException(requestMessage, error, description, authenticateHeader: wwwAuthenticateHeader); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs index f555248..6a96c2d 100644 --- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs @@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2 { using System.Security.Cryptography; using System.Text; using System.Web; - + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; @@ -23,12 +23,30 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> public class AuthorizationServer { /// <summary> + /// A reusable instance of the scope satisfied checker. + /// </summary> + private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck(); + + /// <summary> + /// The list of modules that verify client authentication data. + /// </summary> + private readonly List<ClientAuthenticationModule> clientAuthenticationModules = new List<ClientAuthenticationModule>(); + + /// <summary> + /// The lone aggregate client authentication module that uses the <see cref="clientAuthenticationModules"/> and applies aggregating policy. + /// </summary> + private readonly ClientAuthenticationModule aggregatingClientAuthenticationModule; + + /// <summary> /// Initializes a new instance of the <see cref="AuthorizationServer"/> class. /// </summary> /// <param name="authorizationServer">The authorization server.</param> - public AuthorizationServer(IAuthorizationServer authorizationServer) { + public AuthorizationServer(IAuthorizationServerHost authorizationServer) { Requires.NotNull(authorizationServer, "authorizationServer"); - this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer); + this.aggregatingClientAuthenticationModule = new AggregatingClientCredentialReader(this.clientAuthenticationModules); + this.Channel = new OAuth2AuthorizationServerChannel(authorizationServer, this.aggregatingClientAuthenticationModule); + this.clientAuthenticationModules.AddRange(OAuth2AuthorizationServerSection.Configuration.ClientAuthenticationModules.CreateInstances(true)); + this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck; } /// <summary> @@ -41,11 +59,26 @@ namespace DotNetOpenAuth.OAuth2 { /// Gets the authorization server. /// </summary> /// <value>The authorization server.</value> - public IAuthorizationServer AuthorizationServerServices { + public IAuthorizationServerHost AuthorizationServerServices { get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; } } /// <summary> + /// Gets the extension modules that can read client authentication data from incoming messages. + /// </summary> + public IList<ClientAuthenticationModule> ClientAuthenticationModules { + get { return this.clientAuthenticationModules; } + } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + public IScopeSatisfiedCheck ScopeSatisfiedCheck { + get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck; } + set { ((IOAuth2ChannelWithAuthorizationServer)this.Channel).ScopeSatisfiedCheck = value; } + } + + /// <summary> /// Reads in a client's request for the Authorization Server to obtain permission from /// the user to authorize the Client's access of some protected resource(s). /// </summary> @@ -63,7 +96,7 @@ namespace DotNetOpenAuth.OAuth2 { if (message.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { // Clients with no secrets can only request implicit grant types. var client = this.AuthorizationServerServices.GetClientOrThrow(message.ClientIdentifier); - ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(client.Secret), Protocol.unauthorized_client); + ErrorUtilities.VerifyProtocol(client.HasNonEmptySecret, Protocol.EndUserAuthorizationRequestErrorCodes.UnauthorizedClient); } } @@ -110,17 +143,30 @@ namespace DotNetOpenAuth.OAuth2 { IProtocolMessage responseMessage; try { if (this.Channel.TryReadFromRequest(request, out requestMessage)) { - // TODO: refreshToken should be set appropriately based on authorization server policy. - responseMessage = this.PrepareAccessTokenResponse(requestMessage); + var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(requestMessage); + ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null."); + + IAccessTokenRequestInternal accessRequestInternal = requestMessage; + accessRequestInternal.AccessTokenResult = accessTokenResult; + + var successResponseMessage = this.PrepareAccessTokenResponse(requestMessage, accessTokenResult.AllowRefreshToken); + successResponseMessage.Lifetime = accessTokenResult.AccessToken.Lifetime; + + var authCarryingRequest = requestMessage as IAuthorizationCarryingRequest; + if (authCarryingRequest != null) { + accessTokenResult.AccessToken.ApplyAuthorization(authCarryingRequest.AuthorizationDescription); + IAccessTokenIssuingResponse accessTokenIssuingResponse = successResponseMessage; + accessTokenIssuingResponse.AuthorizationDescription = accessTokenResult.AccessToken; + } + + responseMessage = successResponseMessage; } else { - responseMessage = new AccessTokenFailedResponse() { - Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest, - }; + responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest }; } + } catch (TokenEndpointProtocolException ex) { + responseMessage = ex.GetResponse(); } catch (ProtocolException) { - responseMessage = new AccessTokenFailedResponse() { - Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest, - }; + responseMessage = new AccessTokenFailedResponse() { Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest }; } return this.Channel.PrepareResponse(responseMessage); @@ -165,12 +211,30 @@ namespace DotNetOpenAuth.OAuth2 { EndUserAuthorizationSuccessResponseBase response; switch (authorizationRequest.ResponseType) { case EndUserAuthorizationResponseType.AccessToken: - var accessTokenResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); - accessTokenResponse.Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime((EndUserAuthorizationImplicitRequest)authorizationRequest); - response = accessTokenResponse; + IAccessTokenRequestInternal accessRequestInternal = (EndUserAuthorizationImplicitRequest)authorizationRequest; + var accessTokenResult = this.AuthorizationServerServices.CreateAccessToken(accessRequestInternal); + ErrorUtilities.VerifyHost(accessTokenResult != null, "IAuthorizationServerHost.CreateAccessToken must not return null."); + + accessRequestInternal.AccessTokenResult = accessTokenResult; + + var implicitGrantResponse = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); + implicitGrantResponse.Lifetime = accessTokenResult.AccessToken.Lifetime; + accessTokenResult.AccessToken.ApplyAuthorization(implicitGrantResponse.Scope, userName, implicitGrantResponse.Lifetime); + + IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse; + tokenCarryingResponse.AuthorizationDescription = accessTokenResult.AccessToken; + + response = implicitGrantResponse; break; case EndUserAuthorizationResponseType.AuthorizationCode: - response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest); + var authCodeResponse = new EndUserAuthorizationSuccessAuthCodeResponseAS(callback, authorizationRequest); + IAuthorizationCodeCarryingRequest codeCarryingResponse = authCodeResponse; + codeCarryingResponse.AuthorizationDescription = new AuthorizationCode( + authorizationRequest.ClientIdentifier, + authorizationRequest.Callback, + authCodeResponse.Scope, + userName); + response = authCodeResponse; break; default: throw ErrorUtilities.ThrowInternal("Unexpected response type."); @@ -208,7 +272,7 @@ namespace DotNetOpenAuth.OAuth2 { // Since the request didn't include a callback URL, look up the callback from // the client's preregistration with this authorization server. Uri defaultCallback = client.DefaultCallback; - ErrorUtilities.VerifyProtocol(defaultCallback != null, OAuthStrings.NoCallback); + ErrorUtilities.VerifyProtocol(defaultCallback != null, AuthServerStrings.NoCallback); return defaultCallback; } @@ -216,24 +280,24 @@ namespace DotNetOpenAuth.OAuth2 { /// Prepares the response to an access token request. /// </summary> /// <param name="request">The request for an access token.</param> - /// <param name="includeRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> + /// <param name="allowRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> /// <returns>The response message to send to the client.</returns> - private IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, bool includeRefreshToken = true) { + private AccessTokenSuccessResponse PrepareAccessTokenResponse(AccessTokenRequestBase request, bool allowRefreshToken = true) { Requires.NotNull(request, "request"); - if (includeRefreshToken) { + if (allowRefreshToken) { if (request is AccessTokenClientCredentialsRequest) { // Per OAuth 2.0 section 4.4.3 (draft 23), refresh tokens should never be included // in a response to an access token request that used the client credential grant type. Logger.OAuth.Debug("Suppressing refresh token in access token response because the grant type used by the client disallows it."); - includeRefreshToken = false; + allowRefreshToken = false; } } var tokenRequest = (IAuthorizationCarryingRequest)request; + var accessTokenRequest = (IAccessTokenRequestInternal)request; var response = new AccessTokenSuccessResponse(request) { - Lifetime = this.AuthorizationServerServices.GetAccessTokenLifetime(request), - HasRefreshToken = includeRefreshToken, + HasRefreshToken = allowRefreshToken, }; response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); return response; diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs new file mode 100644 index 0000000..c577a0a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServerAccessToken.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationServerAccessToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// An access token minted by the authorization server that can be serialized for transmission to the client. + /// </summary> + public class AuthorizationServerAccessToken : AccessToken { + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationServerAccessToken"/> class. + /// </summary> + public AuthorizationServerAccessToken() { + } + + /// <summary> + /// Gets or sets the crypto service provider with the asymmetric private key to use for signing access tokens. + /// </summary> + /// <returns>A crypto service provider instance that contains the private key.</returns> + /// <value>Must not be null, and must contain the private key.</value> + /// <remarks> + /// The public key in the private/public key pair will be used by the resource + /// servers to validate that the access token is minted by a trusted authorization server. + /// </remarks> + public RSACryptoServiceProvider AccessTokenSigningKey { get; set; } + + /// <summary> + /// Gets or sets the key to encrypt the access token. + /// </summary> + public RSACryptoServiceProvider ResourceServerEncryptionKey { get; set; } + + /// <summary> + /// Serializes this instance to a simple string for transmission to the client. + /// </summary> + /// <returns>A non-empty string.</returns> + protected internal override string Serialize() { + var formatter = CreateFormatter(this.AccessTokenSigningKey, this.ResourceServerEncryptionKey); + return formatter.Serialize(this); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs new file mode 100644 index 0000000..ace95b3 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AggregatingClientCredentialReader.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="AggregatingClientCredentialReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Applies OAuth 2 spec policy for supporting multiple methods of client authentication. + /// </summary> + internal class AggregatingClientCredentialReader : ClientAuthenticationModule { + /// <summary> + /// The set of authenticators to apply to an incoming request. + /// </summary> + private readonly IEnumerable<ClientAuthenticationModule> authenticators; + + /// <summary> + /// Initializes a new instance of the <see cref="AggregatingClientCredentialReader"/> class. + /// </summary> + /// <param name="authenticators">The set of authentication modules to apply.</param> + internal AggregatingClientCredentialReader(IEnumerable<ClientAuthenticationModule> authenticators) { + Requires.NotNull(authenticators, "readers"); + this.authenticators = authenticators; + } + + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public override string AuthenticateHeader { + get { + var builder = new StringBuilder(); + foreach (var authenticator in this.authenticators) { + string scheme = authenticator.AuthenticateHeader; + if (scheme != null) { + if (builder.Length > 0) { + builder.Append(", "); + } + + builder.Append(scheme); + } + } + + return builder.Length > 0 ? builder.ToString() : null; + } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + ClientAuthenticationModule authenticator = null; + ClientAuthenticationResult result = ClientAuthenticationResult.NoAuthenticationRecognized; + clientIdentifier = null; + + foreach (var candidateAuthenticator in this.authenticators) { + string candidateClientIdentifier; + var resultCandidate = candidateAuthenticator.TryAuthenticateClient(authorizationServerHost, requestMessage, out candidateClientIdentifier); + + ErrorUtilities.VerifyProtocol( + result == ClientAuthenticationResult.NoAuthenticationRecognized || resultCandidate == ClientAuthenticationResult.NoAuthenticationRecognized, + "Message rejected because multiple forms of client authentication ({0} and {1}) were detected, which is forbidden by the OAuth 2 Protocol Framework specification.", + authenticator, + candidateAuthenticator); + + if (resultCandidate != ClientAuthenticationResult.NoAuthenticationRecognized) { + authenticator = candidateAuthenticator; + result = resultCandidate; + clientIdentifier = candidateClientIdentifier; + } + } + + return result; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerBindingElementBase.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs index 49f820d..9d3a52c 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerBindingElementBase.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthServerBindingElementBase.cs @@ -38,10 +38,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { public abstract MessageProtections Protection { get; } /// <summary> + /// Gets the channel to which this binding element belongs. + /// </summary> + internal IOAuth2ChannelWithAuthorizationServer AuthServerChannel { + get { return (IOAuth2ChannelWithAuthorizationServer)this.Channel; } + } + + /// <summary> /// Gets the authorization server hosting this channel. /// </summary> /// <value>The authorization server.</value> - protected IAuthorizationServer AuthorizationServer { + protected IAuthorizationServerHost AuthorizationServer { get { return ((IOAuth2ChannelWithAuthorizationServer)this.Channel).AuthorizationServer; } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs index 111c007..853a629 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCode.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/AuthorizationCode.cs @@ -47,6 +47,14 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } /// <summary> + /// Gets the maximum message age from the standard expiration binding element. + /// </summary> + /// <value>This interval need not account for clock skew because it is only compared within a single authorization server or farm of servers.</value> + internal static TimeSpan MaximumMessageAge { + get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetimeNoSkew; } + } + + /// <summary> /// Gets or sets the hash of the callback URL. /// </summary> [MessagePart("cb")] @@ -57,7 +65,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </summary> /// <param name="authorizationServer">The authorization server that will be serializing/deserializing this authorization code. Must not be null.</param> /// <returns>A DataBag formatter.</returns> - internal static IDataBagFormatter<AuthorizationCode> CreateFormatter(IAuthorizationServer authorizationServer) { + internal static IDataBagFormatter<AuthorizationCode> CreateFormatter(IAuthorizationServerHost authorizationServer) { Requires.NotNull(authorizationServer, "authorizationServer"); Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null); @@ -70,8 +78,8 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { signed: true, encrypted: true, compressed: false, - maximumAge: AuthorizationCodeBindingElement.MaximumMessageAge, - decodeOnceOnly: authorizationServer.VerificationCodeNonceStore); + maximumAge: MaximumMessageAge, + decodeOnceOnly: authorizationServer.NonceStore); } /// <summary> @@ -86,7 +94,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "redirecturimismatch", Justification = "Protocol requirement")] [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] internal void VerifyCallback(Uri callback) { - ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalent(this.CallbackHash, CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch); + ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalentConstantTime(this.CallbackHash, CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch); } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs new file mode 100644 index 0000000..027929a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientAuthenticationModule.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientAuthenticationModule.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A base class for extensions that can read incoming messages and extract the client identifier and + /// possibly authentication information (like a shared secret, signed nonce, etc.) + /// </summary> + public abstract class ClientAuthenticationModule { + /// <summary> + /// Initializes a new instance of the <see cref="ClientAuthenticationModule"/> class. + /// </summary> + protected ClientAuthenticationModule() { + } + + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public virtual string AuthenticateHeader { + get { return null; } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public abstract ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier); + + /// <summary> + /// Validates a client identifier and shared secret against the authoriation server's database. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host; cannot be <c>null</c>.</param> + /// <param name="clientIdentifier">The alleged client identifier.</param> + /// <param name="clientSecret">The alleged client secret to be verified.</param> + /// <returns>An indication as to the outcome of the validation.</returns> + protected static ClientAuthenticationResult TryAuthenticateClientBySecret(IAuthorizationServerHost authorizationServerHost, string clientIdentifier, string clientSecret) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + + if (!string.IsNullOrEmpty(clientIdentifier)) { + var client = authorizationServerHost.GetClient(clientIdentifier); + if (client != null) { + if (!string.IsNullOrEmpty(clientSecret)) { + if (client.IsValidClientSecret(clientSecret)) { + return ClientAuthenticationResult.ClientAuthenticated; + } else { // invalid client secret + return ClientAuthenticationResult.ClientAuthenticationRejected; + } + } else { // no client secret provided + return ClientAuthenticationResult.ClientIdNotAuthenticated; + } + } else { // The client identifier is not recognized. + return ClientAuthenticationResult.ClientAuthenticationRejected; + } + } else { // no client id provided. + return ClientAuthenticationResult.NoAuthenticationRecognized; + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs new file mode 100644 index 0000000..655d38f --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialHttpBasicReader.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialHttpBasicReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Reads client authentication information from the HTTP Authorization header via Basic authentication. + /// </summary> + public class ClientCredentialHttpBasicReader : ClientAuthenticationModule { + /// <summary> + /// Gets this module's contribution to an HTTP 401 WWW-Authenticate header so the client knows what kind of authentication this module supports. + /// </summary> + public override string AuthenticateHeader { + get { return "Basic"; } + } + + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + var credential = OAuthUtilities.ParseHttpBasicAuth(requestMessage.Headers); + if (credential != null) { + clientIdentifier = credential.UserName; + return TryAuthenticateClientBySecret(authorizationServerHost, credential.UserName, credential.Password); + } + + clientIdentifier = null; + return ClientAuthenticationResult.NoAuthenticationRecognized; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs new file mode 100644 index 0000000..2afd06e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/ClientCredentialMessagePartReader.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialMessagePartReader.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Web; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Reads client authentication information from the message payload itself (POST entity as a URI-encoded parameter). + /// </summary> + public class ClientCredentialMessagePartReader : ClientAuthenticationModule { + /// <summary> + /// Attempts to extract client identification/authentication information from a message. + /// </summary> + /// <param name="authorizationServerHost">The authorization server host.</param> + /// <param name="requestMessage">The incoming message.</param> + /// <param name="clientIdentifier">Receives the client identifier, if one was found.</param> + /// <returns>The level of the extracted client information.</returns> + public override ClientAuthenticationResult TryAuthenticateClient(IAuthorizationServerHost authorizationServerHost, AuthenticatedClientRequestBase requestMessage, out string clientIdentifier) { + Requires.NotNull(authorizationServerHost, "authorizationServerHost"); + Requires.NotNull(requestMessage, "requestMessage"); + + clientIdentifier = requestMessage.ClientIdentifier; + return TryAuthenticateClientBySecret(authorizationServerHost, requestMessage.ClientIdentifier, requestMessage.ClientSecret); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs index 5fc73ce..5247062 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/IOAuth2ChannelWithAuthorizationServer.cs @@ -14,6 +14,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Gets the authorization server. /// </summary> /// <value>The authorization server.</value> - IAuthorizationServer AuthorizationServer { get; } + IAuthorizationServerHost AuthorizationServer { get; } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } } } diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs new file mode 100644 index 0000000..80b843a --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------- +// <copyright file="MessageValidationBindingElement.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.Messages; + using Messaging; + + /// <summary> + /// A guard for all messages to or from an Authorization Server to ensure that they are well formed, + /// have valid secrets, callback URIs, etc. + /// </summary> + /// <remarks> + /// This binding element also ensures that the code/token coming in is issued to + /// the same client that is sending the code/token and that the authorization has + /// not been revoked and that an access token has not expired. + /// </remarks> + internal class MessageValidationBindingElement : AuthServerBindingElementBase { + /// <summary> + /// The aggregating client authentication module. + /// </summary> + private readonly ClientAuthenticationModule clientAuthenticationModule; + + /// <summary> + /// Initializes a new instance of the <see cref="MessageValidationBindingElement"/> class. + /// </summary> + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> + internal MessageValidationBindingElement(ClientAuthenticationModule clientAuthenticationModule) { + Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule"); + this.clientAuthenticationModule = clientAuthenticationModule; + } + + /// <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 override 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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var accessTokenResponse = message as AccessTokenSuccessResponse; + if (accessTokenResponse != null) { + var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse; + var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest; + ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.ClientCredentials || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken); + } + + 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 override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + bool applied = false; + + // Check that the client secret is correct for client authenticated messages. + var clientCredentialOnly = message as AccessTokenClientCredentialsRequest; + var authenticatedClientRequest = message as AuthenticatedClientRequestBase; + var accessTokenRequest = authenticatedClientRequest as AccessTokenRequestBase; // currently the only type of message. + var resourceOwnerPasswordCarrier = message as AccessTokenResourceOwnerPasswordCredentialsRequest; + if (authenticatedClientRequest != null) { + string clientIdentifier; + var result = this.clientAuthenticationModule.TryAuthenticateClient(this.AuthServerChannel.AuthorizationServer, authenticatedClientRequest, out clientIdentifier); + switch (result) { + case ClientAuthenticationResult.ClientAuthenticated: + break; + case ClientAuthenticationResult.NoAuthenticationRecognized: + case ClientAuthenticationResult.ClientIdNotAuthenticated: + // The only grant type that allows no client credentials is the resource owner credentials grant. + AuthServerUtilities.TokenEndpointVerify(resourceOwnerPasswordCarrier != null, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch); + break; + default: + AuthServerUtilities.TokenEndpointVerify(false, accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient, this.clientAuthenticationModule, AuthServerStrings.ClientSecretMismatch); + break; + } + + authenticatedClientRequest.ClientIdentifier = result == ClientAuthenticationResult.NoAuthenticationRecognized ? null : clientIdentifier; + accessTokenRequest.ClientAuthenticated = result == ClientAuthenticationResult.ClientAuthenticated; + applied = true; + } + + // Check that any resource owner password credential is correct. + if (resourceOwnerPasswordCarrier != null) { + try { + string canonicalUserName; + if (this.AuthorizationServer.TryAuthorizeResourceOwnerCredentialGrant(resourceOwnerPasswordCarrier.UserName, resourceOwnerPasswordCarrier.Password, resourceOwnerPasswordCarrier, out canonicalUserName)) { + ErrorUtilities.VerifyHost(!string.IsNullOrEmpty(canonicalUserName), "IsResourceOwnerCredentialValid did not initialize out parameter."); + resourceOwnerPasswordCarrier.CredentialsValidated = true; + resourceOwnerPasswordCarrier.UserName = canonicalUserName; + } else { + Logger.OAuth.ErrorFormat( + "Resource owner password credential for user \"{0}\" rejected by authorization server host.", + resourceOwnerPasswordCarrier.UserName); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant, AuthServerStrings.InvalidResourceOwnerPasswordCredential); + } + } catch (NotSupportedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } catch (NotImplementedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } + + applied = true; + } else if (clientCredentialOnly != null) { + try { + if (!this.AuthorizationServer.TryAuthorizeClientCredentialsGrant(clientCredentialOnly)) { + Logger.OAuth.ErrorFormat( + "Client credentials grant access request for client \"{0}\" rejected by authorization server host.", + clientCredentialOnly.ClientIdentifier); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnauthorizedClient); + } + } catch (NotSupportedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } catch (NotImplementedException) { + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType); + } + } else { + // Check that authorization requests come with an acceptable callback URI. + var authorizationRequest = message as EndUserAuthorizationRequest; + if (authorizationRequest != null) { + var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier); + ErrorUtilities.VerifyProtocol(authorizationRequest.Callback == null || client.IsCallbackAllowed(authorizationRequest.Callback), AuthServerStrings.ClientCallbackDisallowed, authorizationRequest.Callback); + ErrorUtilities.VerifyProtocol(authorizationRequest.Callback != null || client.DefaultCallback != null, AuthServerStrings.NoCallback); + applied = true; + } + + // Check that the callback URI in a direct message from the client matches the one in the indirect message received earlier. + var request = message as AccessTokenAuthorizationCodeRequestAS; + if (request != null) { + IAuthorizationCodeCarryingRequest tokenRequest = request; + tokenRequest.AuthorizationDescription.VerifyCallback(request.Callback); + applied = true; + } + + var authCarrier = message as IAuthorizationCarryingRequest; + if (authCarrier != null) { + var accessRequest = authCarrier as AccessTokenRequestBase; + if (accessRequest != null) { + // Make sure the client sending us this token is the client we issued the token to. + AuthServerUtilities.TokenEndpointVerify(string.Equals(accessRequest.ClientIdentifier, authCarrier.AuthorizationDescription.ClientIdentifier, StringComparison.Ordinal), accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidClient); + + var scopedAccessRequest = accessRequest as ScopedAccessTokenRequest; + if (scopedAccessRequest != null) { + // Make sure the scope the client is requesting does not exceed the scope in the grant. + if (!this.AuthServerChannel.ScopeSatisfiedCheck.IsScopeSatisfied(requiredScope: scopedAccessRequest.Scope, grantedScope: authCarrier.AuthorizationDescription.Scope)) { + Logger.OAuth.ErrorFormat("The requested access scope (\"{0}\") exceeds the grant scope (\"{1}\").", scopedAccessRequest.Scope, authCarrier.AuthorizationDescription.Scope); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidScope, AuthServerStrings.AccessScopeExceedsGrantScope); + } + } + } + + // Make sure the authorization this token represents hasn't already been revoked. + if (!this.AuthorizationServer.IsAuthorizationValid(authCarrier.AuthorizationDescription)) { + Logger.OAuth.Error("Rejecting access token request because the IAuthorizationServerHost.IsAuthorizationValid method returned false."); + throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant); + } + + applied = true; + } + } + + return applied ? (MessageProtections?)MessageProtections.None : null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs index 6717717..7ca4538 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs @@ -11,17 +11,33 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System.Net.Mime; using System.Web; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.AuthServer.Messages; + using DotNetOpenAuth.OAuth2.Messages; /// <summary> /// The channel for the OAuth protocol. /// </summary> internal class OAuth2AuthorizationServerChannel : OAuth2ChannelBase, IOAuth2ChannelWithAuthorizationServer { /// <summary> + /// The messages receivable by this channel. + /// </summary> + private static readonly Type[] MessageTypes = new Type[] { + typeof(AccessTokenRefreshRequestAS), + typeof(AccessTokenAuthorizationCodeRequestAS), + typeof(AccessTokenResourceOwnerPasswordCredentialsRequest), + typeof(AccessTokenClientCredentialsRequest), + typeof(EndUserAuthorizationRequest), + typeof(EndUserAuthorizationImplicitRequest), + typeof(EndUserAuthorizationFailedResponse), + }; + + /// <summary> /// Initializes a new instance of the <see cref="OAuth2AuthorizationServerChannel"/> class. /// </summary> /// <param name="authorizationServer">The authorization server.</param> - protected internal OAuth2AuthorizationServerChannel(IAuthorizationServer authorizationServer) - : base(InitializeBindingElements(authorizationServer)) { + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> + protected internal OAuth2AuthorizationServerChannel(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule) + : base(MessageTypes, InitializeBindingElements(authorizationServer, clientAuthenticationModule)) { Requires.NotNull(authorizationServer, "authorizationServer"); this.AuthorizationServer = authorizationServer; } @@ -30,7 +46,12 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Gets the authorization server. /// </summary> /// <value>The authorization server.</value> - public IAuthorizationServer AuthorizationServer { get; private set; } + public IAuthorizationServerHost AuthorizationServer { get; private set; } + + /// <summary> + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. + /// </summary> + public IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } /// <summary> /// Gets the protocol message that may be in the given HTTP response. @@ -91,17 +112,19 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Initializes the binding elements for the OAuth channel. /// </summary> /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientAuthenticationModule">The aggregating client authentication module.</param> /// <returns> /// An array of binding elements used to initialize the channel. /// </returns> - private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) { + private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServerHost authorizationServer, ClientAuthenticationModule clientAuthenticationModule) { Requires.NotNull(authorizationServer, "authorizationServer"); + Requires.NotNull(clientAuthenticationModule, "clientAuthenticationModule"); + var bindingElements = new List<IChannelBindingElement>(); - bindingElements.Add(new AuthServerAllFlowsBindingElement()); - bindingElements.Add(new AuthorizationCodeBindingElement()); - bindingElements.Add(new AccessTokenBindingElement()); - bindingElements.Add(new AccessRequestBindingElement()); + // The order they are provided is used for outgoing messgaes, and reversed for incoming messages. + bindingElements.Add(new MessageValidationBindingElement(clientAuthenticationModule)); + bindingElements.Add(new TokenCodeSerializationBindingElement()); return bindingElements.ToArray(); } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs index 993583c..993583c 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/RefreshToken.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/RefreshToken.cs diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs new file mode 100644 index 0000000..494a10b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenCodeSerializationBindingElement.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.AuthServer.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Serializes and deserializes authorization codes, refresh tokens and access tokens + /// on incoming and outgoing messages. + /// </summary> + internal class TokenCodeSerializationBindingElement : AuthServerBindingElementBase { + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <value></value> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public override 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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + var directResponse = message as IDirectResponseProtocolMessage; + var request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequestInternal : null; + + // Serialize the authorization code, if there is one. + var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; + if (authCodeCarrier != null) { + var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); + var code = authCodeCarrier.AuthorizationDescription; + authCodeCarrier.Code = codeFormatter.Serialize(code); + return MessageProtections.None; + } + + // Serialize the refresh token, if applicable. + var refreshTokenResponse = message as AccessTokenSuccessResponse; + if (refreshTokenResponse != null && refreshTokenResponse.HasRefreshToken) { + var refreshTokenCarrier = (IAuthorizationCarryingRequest)message; + var refreshToken = new RefreshToken(refreshTokenCarrier.AuthorizationDescription); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); + refreshTokenResponse.RefreshToken = refreshTokenFormatter.Serialize(refreshToken); + } + + // Serialize the access token, if applicable. + var accessTokenResponse = message as IAccessTokenIssuingResponse; + if (accessTokenResponse != null && accessTokenResponse.AuthorizationDescription != null) { + ErrorUtilities.VerifyInternal(request != null, "We should always have a direct request message for this case."); + accessTokenResponse.AccessToken = accessTokenResponse.AuthorizationDescription.Serialize(); + } + + 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> + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "incorrectclientcredentials", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "authorizationexpired", Justification = "Protocol requirement")] + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] + public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; + if (authCodeCarrier != null) { + var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); + var authorizationCode = new AuthorizationCode(); + authorizationCodeFormatter.Deserialize(authorizationCode, message, authCodeCarrier.Code, Protocol.code); + authCodeCarrier.AuthorizationDescription = authorizationCode; + } + + var refreshTokenCarrier = message as IRefreshTokenCarryingRequest; + if (refreshTokenCarrier != null) { + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); + var refreshToken = new RefreshToken(); + refreshTokenFormatter.Deserialize(refreshToken, message, refreshTokenCarrier.RefreshToken, Protocol.refresh_token); + refreshTokenCarrier.AuthorizationDescription = refreshToken; + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ClientDescription.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs index 76c3ea6..3384183 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ClientDescription.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ClientDescription.cs @@ -9,15 +9,16 @@ namespace DotNetOpenAuth.OAuth2 { using System.Collections.Generic; using System.Linq; using System.Text; + using DotNetOpenAuth.Messaging; /// <summary> /// A default implementation of the <see cref="IClientDescription"/> interface. /// </summary> public class ClientDescription : IClientDescription { /// <summary> - /// A delegate that determines whether the callback is allowed. + /// The client's secret, if any. /// </summary> - private readonly Func<Uri, bool> isCallbackAllowed; + private readonly string secret; /// <summary> /// Initializes a new instance of the <see cref="ClientDescription"/> class. @@ -25,18 +26,13 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="secret">The secret.</param> /// <param name="defaultCallback">The default callback.</param> /// <param name="clientType">Type of the client.</param> - /// <param name="isCallbackAllowed">A delegate that determines whether the callback is allowed.</param> - public ClientDescription(string secret, Uri defaultCallback, ClientType clientType, Func<Uri, bool> isCallbackAllowed = null) { - this.Secret = secret; + public ClientDescription(string secret, Uri defaultCallback, ClientType clientType) { + this.secret = secret; this.DefaultCallback = defaultCallback; this.ClientType = clientType; - this.isCallbackAllowed = isCallbackAllowed; } - /// <summary> - /// Gets the client secret. - /// </summary> - public string Secret { get; private set; } + #region IClientDescription Members /// <summary> /// Gets the callback to use when an individual authorization request @@ -53,19 +49,42 @@ namespace DotNetOpenAuth.OAuth2 { public ClientType ClientType { get; private set; } /// <summary> + /// Gets a value indicating whether a non-empty secret is registered for this client. + /// </summary> + public virtual bool HasNonEmptySecret { + get { return !string.IsNullOrEmpty(this.secret); } + } + + /// <summary> /// Determines whether a callback URI included in a client's authorization request /// is among those allowed callbacks for the registered client. /// </summary> - /// <param name="callback">The absolute URI the client has requested the authorization result be received at.</param> + /// <param name="callback">The absolute URI the client has requested the authorization result be received at. Never null.</param> /// <returns> /// <c>true</c> if the callback URL is allowable for this client; otherwise, <c>false</c>. /// </returns> - public bool IsCallbackAllowed(Uri callback) { - if (this.isCallbackAllowed != null) { - return this.isCallbackAllowed(callback); - } - + /// <remarks> + /// This method may be overridden to allow for several callbacks to match. + /// </remarks> + public virtual bool IsCallbackAllowed(Uri callback) { return EqualityComparer<Uri>.Default.Equals(this.DefaultCallback, callback); } + + /// <summary> + /// Checks whether the specified client secret is correct. + /// </summary> + /// <param name="secret">The secret obtained from the client.</param> + /// <returns><c>true</c> if the secret matches the one in the authorization server's record for the client; <c>false</c> otherwise.</returns> + /// <remarks> + /// All string equality checks, whether checking secrets or their hashes, + /// should be done using <see cref="MessagingUtilities.EqualsConstantTime"/> to mitigate timing attacks. + /// </remarks> + public virtual bool IsValidClientSecret(string secret) { + Requires.NotNullOrEmpty(secret, "secret"); + + return MessagingUtilities.EqualsConstantTime(secret, this.secret); + } + + #endregion } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs index a0a2ad9..b75cb29 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationServer.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// <copyright file="IAuthorizationServer.cs" company="Outercurve Foundation"> +// <copyright file="IAuthorizationServerHost.cs" company="Outercurve Foundation"> // Copyright (c) Outercurve Foundation. All rights reserved. // </copyright> //----------------------------------------------------------------------- @@ -19,8 +19,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Provides host-specific authorization server services needed by this library. /// </summary> - [ContractClass(typeof(IAuthorizationServerContract))] - public interface IAuthorizationServer { + [ContractClass(typeof(IAuthorizationServerHostContract))] + public interface IAuthorizationServerHost { /// <summary> /// Gets the store for storing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> @@ -35,52 +35,18 @@ namespace DotNetOpenAuth.OAuth2 { /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// </summary> /// <value>The authorization code nonce store.</value> - INonceStore VerificationCodeNonceStore { get; } + INonceStore NonceStore { get; } /// <summary> - /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. + /// Acquires the access token and related parameters that go into the formulation of the token endpoint's response to a client. /// </summary> - /// <returns>A crypto service provider instance that contains the private key.</returns> - /// <value>Must not be null, and must contain the private key.</value> - /// <remarks> - /// The public key in the private/public key pair will be used by the resource - /// servers to validate that the access token is minted by a trusted authorization server. - /// </remarks> - RSACryptoServiceProvider AccessTokenSigningKey { get; } - - /// <summary> - /// Obtains the lifetime for a new access token. - /// </summary> - /// <param name="accessTokenRequestMessage"> - /// Details regarding the resources that the access token will grant access to, and the identity of the client - /// that will receive that access. - /// Based on this information the receiving resource server can be determined and the lifetime of the access - /// token can be set based on the sensitivity of the resources. - /// </param> - /// <returns> - /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. - /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or - /// the resources to which access is being granted are sensitive. - /// </returns> - TimeSpan GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage); - - /// <summary> - /// Obtains the encryption key for an access token being created. - /// </summary> - /// <param name="accessTokenRequestMessage"> - /// Details regarding the resources that the access token will grant access to, and the identity of the client + /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client /// that will receive that access. /// Based on this information the receiving resource server can be determined and the lifetime of the access /// token can be set based on the sensitivity of the resources. /// </param> - /// <returns> - /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. - /// The caller is responsible to dispose of this value. - /// </returns> - /// <remarks> - /// The caller is responsible to dispose of the returned value. - /// </remarks> - RSACryptoServiceProvider GetResourceServerEncryptionKey(IAccessTokenRequest accessTokenRequestMessage); + /// <returns>A non-null parameters instance that DotNetOpenAuth will dispose after it has been used.</returns> + AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage); /// <summary> /// Gets the client with a given identifier. @@ -115,32 +81,61 @@ namespace DotNetOpenAuth.OAuth2 { bool IsAuthorizationValid(IAuthorizationDescription authorization); /// <summary> - /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database. + /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid"/> would + /// return <c>true</c>. /// </summary> /// <param name="userName">Username on the account.</param> /// <param name="password">The user's password.</param> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <param name="canonicalUserName"> + /// Receives the canonical username (normalized for the resource server) of the user, for valid credentials; + /// Or <c>null</c> if the return value is false. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the resource owner password credential grant type. + /// </exception> + bool TryAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest, out string canonicalUserName); + + /// <summary> + /// Determines whether an access token request given a client credential grant should be authorized + /// and if so records an authorization entry such that subsequent calls to <see cref="IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> /// <returns> - /// <c>true</c> if the given credentials are valid; otherwise, <c>false</c>. + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. /// </returns> - /// <exception cref="NotSupportedException">May be thrown if the authorization server does not support the resource owner password credential grant type.</exception> - bool IsResourceOwnerCredentialValid(string userName, string password); + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the client credential grant type. + /// </exception> + bool TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest); } /// <summary> - /// Code Contract for the <see cref="IAuthorizationServer"/> interface. + /// Code Contract for the <see cref="IAuthorizationServerHost"/> interface. /// </summary> - [ContractClassFor(typeof(IAuthorizationServer))] - internal abstract class IAuthorizationServerContract : IAuthorizationServer { + [ContractClassFor(typeof(IAuthorizationServerHost))] + internal abstract class IAuthorizationServerHostContract : IAuthorizationServerHost { /// <summary> - /// Prevents a default instance of the <see cref="IAuthorizationServerContract"/> class from being created. + /// Prevents a default instance of the <see cref="IAuthorizationServerHostContract"/> class from being created. /// </summary> - private IAuthorizationServerContract() { + private IAuthorizationServerHostContract() { } /// <summary> /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> - ICryptoKeyStore IAuthorizationServer.CryptoKeyStore { + ICryptoKeyStore IAuthorizationServerHost.CryptoKeyStore { get { Contract.Ensures(Contract.Result<ICryptoKeyStore>() != null); throw new NotImplementedException(); @@ -151,7 +146,7 @@ namespace DotNetOpenAuth.OAuth2 { /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// </summary> /// <value>The authorization code nonce store.</value> - INonceStore IAuthorizationServer.VerificationCodeNonceStore { + INonceStore IAuthorizationServerHost.NonceStore { get { Contract.Ensures(Contract.Result<INonceStore>() != null); throw new NotImplementedException(); @@ -159,61 +154,12 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. - /// </summary> - /// <value> - /// Must not be null, and must contain the private key. - /// </value> - /// <returns>A crypto service provider instance that contains the private key.</returns> - RSACryptoServiceProvider IAuthorizationServer.AccessTokenSigningKey { - get { - Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != null); - Contract.Ensures(!Contract.Result<RSACryptoServiceProvider>().PublicOnly); - throw new NotImplementedException(); - } - } - - /// <summary> - /// Obtains the lifetime for a new access token. - /// </summary> - /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client - /// that will receive that access. - /// Based on this information the receiving resource server can be determined and the lifetime of the access - /// token can be set based on the sensitivity of the resources.</param> - /// <returns> - /// Receives the lifetime for this access token. Note that within this lifetime, authorization <i>may</i> not be revokable. - /// Short lifetimes are recommended (i.e. one hour), particularly when the client is not authenticated or - /// the resources to which access is being granted are sensitive. - /// </returns> - TimeSpan IAuthorizationServer.GetAccessTokenLifetime(IAccessTokenRequest accessTokenRequestMessage) { - Requires.NotNull(accessTokenRequestMessage, "accessTokenRequestMessage"); - throw new NotImplementedException(); - } - - /// <summary> - /// Obtains the encryption key for an access token being created. - /// </summary> - /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client - /// that will receive that access. - /// Based on this information the receiving resource server can be determined and the lifetime of the access - /// token can be set based on the sensitivity of the resources.</param> - /// <returns> - /// The crypto service provider with the asymmetric public key to use for encrypting access tokens for a specific resource server. - /// The caller is responsible to dispose of this value. - /// </returns> - RSACryptoServiceProvider IAuthorizationServer.GetResourceServerEncryptionKey(IAccessTokenRequest accessTokenRequestMessage) { - Requires.NotNull(accessTokenRequestMessage, "accessTokenRequestMessage"); - Contract.Ensures(Contract.Result<RSACryptoServiceProvider>() != null); - throw new NotImplementedException(); - } - - /// <summary> /// Gets the client with a given identifier. /// </summary> /// <param name="clientIdentifier">The client identifier.</param> /// <returns>The client registration. Never null.</returns> /// <exception cref="ArgumentException">Thrown when no client with the given identifier is registered with this authorization server.</exception> - IClientDescription IAuthorizationServer.GetClient(string clientIdentifier) { + IClientDescription IAuthorizationServerHost.GetClient(string clientIdentifier) { Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); Contract.Ensures(Contract.Result<IClientDescription>() != null); throw new NotImplementedException(); @@ -241,23 +187,72 @@ namespace DotNetOpenAuth.OAuth2 { /// security in the event the user was revoking access in order to sever authorization on a stolen /// account or piece of hardware in which the tokens were stored. </para> /// </remarks> - bool IAuthorizationServer.IsAuthorizationValid(IAuthorizationDescription authorization) { + bool IAuthorizationServerHost.IsAuthorizationValid(IAuthorizationDescription authorization) { Requires.NotNull(authorization, "authorization"); throw new NotImplementedException(); } /// <summary> - /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database. + /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database + /// and if so records an authorization entry such that subsequent calls to <see cref="IAuthorizationServerHost.IsAuthorizationValid"/> would + /// return <c>true</c>. /// </summary> /// <param name="userName">Username on the account.</param> /// <param name="password">The user's password.</param> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <param name="canonicalUserName"> + /// Receives the canonical username (normalized for the resource server) of the user, for valid credentials; + /// Or <c>null</c> if the return value is false. + /// </param> /// <returns> - /// <c>true</c> if the given credentials are valid; otherwise, <c>false</c>. + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. /// </returns> - /// <exception cref="NotSupportedException">May be thrown if the authorization server does not support the resource owner password credential grant type.</exception> - bool IAuthorizationServer.IsResourceOwnerCredentialValid(string userName, string password) { + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the resource owner password credential grant type. + /// </exception> + bool IAuthorizationServerHost.TryAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest, out string canonicalUserName) { Contract.Requires(!string.IsNullOrEmpty(userName)); Contract.Requires(password != null); + Contract.Requires(accessRequest != null); + Contract.Ensures(!Contract.Result<bool>() || !string.IsNullOrEmpty(Contract.ValueAtReturn<string>(out canonicalUserName))); + throw new NotImplementedException(); + } + + /// <summary> + /// Determines whether an access token request given a client credential grant should be authorized + /// and if so records an authorization entry such that subsequent calls to <see cref="IAuthorizationServerHost.IsAuthorizationValid"/> would + /// return <c>true</c>. + /// </summary> + /// <param name="accessRequest"> + /// The access request the credentials came with. + /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. + /// </param> + /// <returns> + /// <c>true</c> if the given credentials are valid and the authorization granted; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="NotSupportedException"> + /// May be thrown if the authorization server does not support the client credential grant type. + /// </exception> + bool IAuthorizationServerHost.TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) { + throw new NotImplementedException(); + } + + /// <summary> + /// Obtains parameters to go into the formulation of an access token. + /// </summary> + /// <param name="accessTokenRequestMessage">Details regarding the resources that the access token will grant access to, and the identity of the client + /// that will receive that access. + /// Based on this information the receiving resource server can be determined and the lifetime of the access + /// token can be set based on the sensitivity of the resources.</param> + /// <returns> + /// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used. + /// </returns> + AccessTokenResult IAuthorizationServerHost.CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) { + Contract.Requires(accessTokenRequestMessage != null); + Contract.Ensures(Contract.Result<AccessTokenResult>() != null); throw new NotImplementedException(); } } diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs new file mode 100644 index 0000000..ca14d0e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenAuthorizationCodeRequestAS.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenAuthorizationCodeRequestAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A request from a Client to an Authorization Server to exchange an authorization code for an access token, + /// and (at the authorization server's option) a refresh token. + /// </summary> + internal class AccessTokenAuthorizationCodeRequestAS : AccessTokenAuthorizationCodeRequest, IAuthorizationCodeCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequestAS"/> class. + /// </summary> + /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> + /// <param name="version">The version.</param> + internal AccessTokenAuthorizationCodeRequestAS(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + } + + #region IAuthorizationCodeCarryingRequest Members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IAuthorizationCodeCarryingRequest.Code { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the code describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs new file mode 100644 index 0000000..d9ca4c8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenRefreshRequestAS.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenRefreshRequestAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.AuthServer.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.AuthServer.ChannelElements; + using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A request from the client to the token endpoint for a new access token + /// in exchange for a refresh token that the client has previously obtained. + /// </summary> + internal class AccessTokenRefreshRequestAS : AccessTokenRefreshRequest, IRefreshTokenCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenRefreshRequestAS"/> class. + /// </summary> + /// <param name="tokenEndpoint">The token endpoint.</param> + /// <param name="version">The version.</param> + internal AccessTokenRefreshRequestAS(Uri tokenEndpoint, Version version) + : base(tokenEndpoint, version) { + } + + #region IRefreshTokenCarryingRequest members + + /// <summary> + /// Gets or sets the verification code or refresh/access token. + /// </summary> + /// <value>The code or token.</value> + string IRefreshTokenCarryingRequest.RefreshToken { + get { return this.RefreshToken; } + set { this.RefreshToken = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + RefreshToken IRefreshTokenCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IRefreshTokenCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs new file mode 100644 index 0000000..25f5dc8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponseAS.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationSuccessAuthCodeResponseAS.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// The message sent by the Authorization Server to the Client via the user agent + /// to indicate that user authorization was granted, carrying an authorization code and possibly an access token, + /// and to return the user to the Client where they started their experience. + /// </summary> + internal class EndUserAuthorizationSuccessAuthCodeResponseAS : EndUserAuthorizationSuccessAuthCodeResponse, IAuthorizationCodeCarryingRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponseAS"/> class. + /// </summary> + /// <param name="clientCallback">The URL to redirect to so the client receives the message. This may not be built into the request message if the client pre-registered the URL with the authorization server.</param> + /// <param name="version">The protocol version.</param> + internal EndUserAuthorizationSuccessAuthCodeResponseAS(Uri clientCallback, Version version) + : base(clientCallback, version) { + Requires.NotNull(version, "version"); + Requires.NotNull(clientCallback, "clientCallback"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponseAS"/> class. + /// </summary> + /// <param name="clientCallback">The URL to redirect to so the client receives the message. This may not be built into the request message if the client pre-registered the URL with the authorization server.</param> + /// <param name="request">The authorization request from the user agent on behalf of the client.</param> + internal EndUserAuthorizationSuccessAuthCodeResponseAS(Uri clientCallback, EndUserAuthorizationRequest request) + : base(clientCallback, request) { + Requires.NotNull(clientCallback, "clientCallback"); + Requires.NotNull(request, "request"); + ((IMessageWithClientState)this).ClientState = request.ClientState; + } + + #region IAuthorizationCodeCarryingRequest Members + + /// <summary> + /// Gets or sets the authorization code. + /// </summary> + string IAuthorizationCodeCarryingRequest.Code { + get { return this.AuthorizationCode; } + set { this.AuthorizationCode = value; } + } + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets the authorization that the code describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationCodeCarryingRequest.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs index 045cb80..045cb80 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationCodeCarryingRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IAuthorizationCodeCarryingRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IRefreshTokenCarryingRequest.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs index ce27538..9e6fc3c 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IRefreshTokenCarryingRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/IRefreshTokenCarryingRequest.cs @@ -4,7 +4,9 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OAuth2.ChannelElements { +namespace DotNetOpenAuth.OAuth2.AuthServer.ChannelElements { + using DotNetOpenAuth.OAuth2.ChannelElements; + /// <summary> /// A message that carries a refresh token between client and authorization server. /// </summary> diff --git a/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs new file mode 100644 index 0000000..1ee5aa5 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ClientSection.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the <oauth2/client> section in the host's .config file. + /// </summary> + internal class OAuth2ClientSection : ConfigurationSection { + /// <summary> + /// The name of the oauth2/client section. + /// </summary> + private const string SectionName = OAuth2SectionGroup.SectionName + "/client"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ClientSection"/> class. + /// </summary> + internal OAuth2ClientSection() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + internal static OAuth2ClientSection Configuration { + get { + Contract.Ensures(Contract.Result<OAuth2ClientSection>() != null); + return (OAuth2ClientSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ClientSection(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj index 3625e54..e72ee1a 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj +++ b/src/DotNetOpenAuth.OAuth2.Client/DotNetOpenAuth.OAuth2.Client.csproj @@ -18,6 +18,18 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2ClientSection.cs" /> + <Compile Include="OAuth2\AuthorizationServerDescription.cs" /> + <Compile Include="OAuth2\AuthorizationState.cs" /> + <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithClient.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" /> + <Compile Include="OAuth2\ClientCredentialApplicator.cs" /> + <Compile Include="OAuth2\IAuthorizationState.cs" /> + <Compile Include="OAuth2\IClientAuthorizationTracker.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestC.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRefreshRequestC.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationImplicitRequestC.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationRequestC.cs" /> <Compile Include="OAuth2\ClientStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> @@ -33,6 +45,10 @@ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> <Name>DotNetOpenAuth.Core</Name> </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj"> + <Project>{CCF3728A-B3D7-404A-9BC6-75197135F2D7}</Project> + <Name>DotNetOpenAuth.OAuth2.ClientAuthorization</Name> + </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> <Name>DotNetOpenAuth.OAuth2</Name> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationServerDescription.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/AuthorizationServerDescription.cs index 38a9ff9..38a9ff9 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationServerDescription.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/AuthorizationServerDescription.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationState.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/AuthorizationState.cs index 4117b3c..4117b3c 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/AuthorizationState.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/AuthorizationState.cs diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs new file mode 100644 index 0000000..c802be6 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="IOAuth2ChannelWithClient.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// An interface that defines the OAuth2 client specific channel additions. + /// </summary> + internal interface IOAuth2ChannelWithClient { + /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the client credentials applicator extension to use. + /// </summary> + ClientCredentialApplicator ClientCredentialApplicator { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs index c9981d3..8ad2ed9 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs @@ -13,18 +13,43 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System.Web; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; /// <summary> /// The messaging channel used by OAuth 2.0 Clients. /// </summary> - internal class OAuth2ClientChannel : OAuth2ChannelBase { + internal class OAuth2ClientChannel : OAuth2ChannelBase, IOAuth2ChannelWithClient { + /// <summary> + /// The messages receivable by this channel. + /// </summary> + private static readonly Type[] MessageTypes = new Type[] { + typeof(AccessTokenSuccessResponse), + typeof(AccessTokenFailedResponse), + typeof(EndUserAuthorizationSuccessAuthCodeResponse), + typeof(EndUserAuthorizationSuccessAccessTokenResponse), + typeof(EndUserAuthorizationFailedResponse), + typeof(UnauthorizedResponse), + }; + /// <summary> /// Initializes a new instance of the <see cref="OAuth2ClientChannel"/> class. /// </summary> - internal OAuth2ClientChannel() { + internal OAuth2ClientChannel() + : base(MessageTypes) { } /// <summary> + /// Gets or sets the identifier by which this client is known to the Authorization Server. + /// </summary> + public string ClientIdentifier { get; set; } + + /// <summary> + /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// </summary> + /// <value>May be <c>null</c> if this client has no client secret.</value> + public ClientCredentialApplicator ClientCredentialApplicator { get; set; } + + /// <summary> /// Prepares an HTTP request that carries a given message. /// </summary> /// <param name="request">The message to send.</param> @@ -65,7 +90,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } else if (response.ContentType.MediaType == HttpFormUrlEncoded || response.ContentType.MediaType == PlainTextEncoded) { return HttpUtility.ParseQueryString(body).ToDictionary(); } else { - throw ErrorUtilities.ThrowProtocol(OAuthStrings.UnexpectedResponseContentType, response.ContentType.MediaType); + throw ErrorUtilities.ThrowProtocol(ClientStrings.UnexpectedResponseContentType, response.ContentType.MediaType); } } @@ -118,5 +143,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { // Clients don't ever send direct responses. throw new NotImplementedException(); } + + /// <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 override void PrepareHttpWebRequest(HttpWebRequest request) { + base.PrepareHttpWebRequest(request); + + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request); + } + } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index eda6bc1..5f377ae 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -26,13 +26,16 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationServer">The token issuer.</param> /// <param name="clientIdentifier">The client identifier.</param> - /// <param name="clientSecret">The client secret.</param> - protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) { + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + protected ClientBase(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, ClientCredentialApplicator clientCredentialApplicator = null) { Requires.NotNull(authorizationServer, "authorizationServer"); this.AuthorizationServer = authorizationServer; this.Channel = new OAuth2ClientChannel(); this.ClientIdentifier = clientIdentifier; - this.ClientSecret = clientSecret; + this.ClientCredentialApplicator = clientCredentialApplicator; } /// <summary> @@ -50,12 +53,26 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Gets or sets the identifier by which this client is known to the Authorization Server. /// </summary> - public string ClientIdentifier { get; set; } + public string ClientIdentifier { + get { return this.OAuthChannel.ClientIdentifier; } + set { this.OAuthChannel.ClientIdentifier = value; } + } + + /// <summary> + /// Gets or sets the tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// </summary> + /// <value>May be <c>null</c> if this client has no client secret.</value> + public ClientCredentialApplicator ClientCredentialApplicator { + get { return this.OAuthChannel.ClientCredentialApplicator; } + set { this.OAuthChannel.ClientCredentialApplicator = value; } + } /// <summary> - /// Gets or sets the client secret shared with the Authorization Server. + /// Gets the OAuth client channel. /// </summary> - public string ClientSecret { get; set; } + internal IOAuth2ChannelWithClient OAuthChannel { + get { return (IOAuth2ChannelWithClient)this.Channel; } + } /// <summary> /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources @@ -116,12 +133,13 @@ namespace DotNetOpenAuth.OAuth2 { } } - var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { + var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, RefreshToken = authorization.RefreshToken, }; + this.ApplyClientCredential(request); + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); UpdateAuthorizationWithResponse(authorization, response); return true; @@ -143,12 +161,13 @@ namespace DotNetOpenAuth.OAuth2 { Requires.NotNull(scope, "scope"); Contract.Ensures(Contract.Result<IAuthorizationState>() != null); - var request = new AccessTokenRefreshRequest(this.AuthorizationServer) { + var request = new AccessTokenRefreshRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, RefreshToken = refreshToken, }; + this.ApplyClientCredential(request); + var response = this.Channel.Request<AccessTokenSuccessResponse>(request); var authorization = new AuthorizationState(); UpdateAuthorizationWithResponse(authorization, response); @@ -248,12 +267,12 @@ namespace DotNetOpenAuth.OAuth2 { Requires.NotNull(authorizationState, "authorizationState"); Requires.NotNull(authorizationSuccess, "authorizationSuccess"); - var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { + var accessTokenRequest = new AccessTokenAuthorizationCodeRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, Callback = authorizationState.Callback, AuthorizationCode = authorizationSuccess.AuthorizationCode, }; + this.ApplyClientCredential(accessTokenRequest); IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; @@ -262,7 +281,28 @@ namespace DotNetOpenAuth.OAuth2 { } else { authorizationState.Delete(); string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; - ErrorUtilities.ThrowProtocol(OAuthStrings.CannotObtainAccessTokenWithReason, error); + ErrorUtilities.ThrowProtocol(ClientStrings.CannotObtainAccessTokenWithReason, error); + } + } + + /// <summary> + /// Applies the default client authentication mechanism given a client secret. + /// </summary> + /// <param name="secret">The client secret. May be <c>null</c></param> + /// <returns>The client credential applicator.</returns> + protected static ClientCredentialApplicator DefaultSecretApplicator(string secret) { + return secret == null ? ClientCredentialApplicator.NoSecret() : ClientCredentialApplicator.NetworkCredential(secret); + } + + /// <summary> + /// Applies any applicable client credential to an authenticated outbound request to the authorization server. + /// </summary> + /// <param name="request">The request to apply authentication information to.</param> + protected void ApplyClientCredential(AuthenticatedClientRequestBase request) { + Requires.NotNull(request, "request"); + + if (this.ClientCredentialApplicator != null) { + this.ClientCredentialApplicator.ApplyClientCredential(this.ClientIdentifier, request); } } @@ -295,7 +335,7 @@ namespace DotNetOpenAuth.OAuth2 { var authorizationState = new AuthorizationState(scopes); request.ClientIdentifier = this.ClientIdentifier; - request.ClientSecret = this.ClientSecret; + this.ApplyClientCredential(request); request.Scope.UnionWith(authorizationState.Scope); var response = this.Channel.Request(request); diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs new file mode 100644 index 0000000..415c893 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientCredentialApplicator.cs @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientCredentialApplicator.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// A base class for extensions that apply client authentication to messages for the authorization server in specific ways. + /// </summary> + public abstract class ClientCredentialApplicator { + /// <summary> + /// Initializes a new instance of the <see cref="ClientCredentialApplicator"/> class. + /// </summary> + protected ClientCredentialApplicator() { + } + + /// <summary> + /// Transmits the secret the client shares with the authorization server as a parameter in the POST entity payload. + /// </summary> + /// <param name="clientSecret">The secret the client shares with the authorization server.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator PostParameter(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + return new PostParameterApplicator(clientSecret); + } + + /// <summary> + /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication. + /// </summary> + /// <param name="credential">The client id and secret.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NetworkCredential(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + return new NetworkCredentialApplicator(credential); + } + + /// <summary> + /// Transmits the client identifier and secret in the HTTP Authorization header via HTTP Basic authentication. + /// </summary> + /// <param name="clientSecret">The secret the client shares with the authorization server.</param> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NetworkCredential(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + return new NetworkCredentialApplicator(clientSecret); + } + + /// <summary> + /// Never transmits a secret. Useful for anonymous clients or clients unable to keep a secret. + /// </summary> + /// <returns>The credential applicator to provide to the <see cref="ClientBase"/> instance.</returns> + public static ClientCredentialApplicator NoSecret() { + return null; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public virtual void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public virtual void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + } + + /// <summary> + /// Authenticates the client via HTTP Basic. + /// </summary> + private class NetworkCredentialApplicator : ClientCredentialApplicator { + /// <summary> + /// The client identifier and secret. + /// </summary> + private readonly NetworkCredential credential; + + /// <summary> + /// The client secret. + /// </summary> + private readonly string clientSecret; + + /// <summary> + /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class. + /// </summary> + /// <param name="clientSecret">The client secret.</param> + internal NetworkCredentialApplicator(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + this.clientSecret = clientSecret; + } + + /// <summary> + /// Initializes a new instance of the <see cref="NetworkCredentialApplicator"/> class. + /// </summary> + /// <param name="credential">The client credential.</param> + internal NetworkCredentialApplicator(NetworkCredential credential) { + Requires.NotNull(credential, "credential"); + this.credential = credential; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + // When using network credentials, the client authentication is not done as standard message parts. + request.ClientIdentifier = null; + request.ClientSecret = null; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, HttpWebRequest request) { + if (clientIdentifier != null) { + if (this.credential != null && this.credential.UserName == clientIdentifier) { + ErrorUtilities.VerifyHost(false, "Client identifiers \"{0}\" and \"{1}\" do not match.", this.credential.UserName, clientIdentifier); + } + + request.Credentials = this.credential ?? new NetworkCredential(clientIdentifier, this.clientSecret); + } + } + } + + /// <summary> + /// Authenticates the client via a client_secret parameter in the message. + /// </summary> + private class PostParameterApplicator : ClientCredentialApplicator { + /// <summary> + /// The client secret. + /// </summary> + private readonly string secret; + + /// <summary> + /// Initializes a new instance of the <see cref="PostParameterApplicator"/> class. + /// </summary> + /// <param name="clientSecret">The client secret.</param> + internal PostParameterApplicator(string clientSecret) { + Requires.NotNullOrEmpty(clientSecret, "clientSecret"); + this.secret = clientSecret; + } + + /// <summary> + /// Applies the client identifier and (when applicable) the client authentication to an outbound message. + /// </summary> + /// <param name="clientIdentifier">The identifier by which the authorization server should recognize this client.</param> + /// <param name="request">The outbound message to apply authentication information to.</param> + public override void ApplyClientCredential(string clientIdentifier, AuthenticatedClientRequestBase request) { + if (clientIdentifier != null) { + request.ClientSecret = this.secret; + } + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.Designer.cs index 9564704..87acfdf 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.17614 +// Runtime Version:4.0.30319.17622 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -88,11 +88,20 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to The property {0} must be set before this operation is allowed.. + /// Looks up a localized string similar to Failed to obtain access token. Authorization Server reports reason: {0}. /// </summary> - internal static string RequiredPropertyNotYetPreset { + internal static string CannotObtainAccessTokenWithReason { get { - return ResourceManager.GetString("RequiredPropertyNotYetPreset", resourceCulture); + return ResourceManager.GetString("CannotObtainAccessTokenWithReason", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unexpected response Content-Type {0}. + /// </summary> + internal static string UnexpectedResponseContentType { + get { + return ResourceManager.GetString("UnexpectedResponseContentType", resourceCulture); } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.resx b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.resx index 0a41e42..5facbc4 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.resx +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientStrings.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="AccessTokenRefreshFailed" xml:space="preserve"> <value>Access token has expired and cannot be automatically refreshed.</value> @@ -127,7 +127,10 @@ <value>Unexpected OAuth authorization response received with callback and client state that does not match an expected value.</value> <comment>The error message generated when detecting a mismatch between the state sent to the authorization server originally and what we got back with successful authorization, or that the user sessions were not identical between the two requests, suggesting XSRF or other attack on the user (victim).</comment> </data> - <data name="RequiredPropertyNotYetPreset" xml:space="preserve"> - <value>The property {0} must be set before this operation is allowed.</value> + <data name="CannotObtainAccessTokenWithReason" xml:space="preserve"> + <value>Failed to obtain access token. Authorization Server reports reason: {0}</value> + </data> + <data name="UnexpectedResponseContentType" xml:space="preserve"> + <value>Unexpected response Content-Type {0}</value> </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationState.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/IAuthorizationState.cs index f38df9a..f38df9a 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/IAuthorizationState.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/IAuthorizationState.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IClientAuthorizationTracker.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/IClientAuthorizationTracker.cs index 73b7a44..73b7a44 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/IClientAuthorizationTracker.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/IClientAuthorizationTracker.cs diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenAuthorizationCodeRequestC.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenAuthorizationCodeRequestC.cs new file mode 100644 index 0000000..ebfb2e8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenAuthorizationCodeRequestC.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenAuthorizationCodeRequestC.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// A request from a Client to an Authorization Server to exchange an authorization code for an access token, + /// and (at the authorization server's option) a refresh token. + /// </summary> + internal class AccessTokenAuthorizationCodeRequestC : AccessTokenAuthorizationCodeRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequestC"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal AccessTokenAuthorizationCodeRequestC(AuthorizationServerDescription authorizationServer) + : base(authorizationServer.TokenEndpoint, authorizationServer.Version) { + Requires.NotNull(authorizationServer, "authorizationServer"); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenRefreshRequestC.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenRefreshRequestC.cs new file mode 100644 index 0000000..25da3dc --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/AccessTokenRefreshRequestC.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenRefreshRequestC.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// A request from the client to the token endpoint for a new access token + /// in exchange for a refresh token that the client has previously obtained. + /// </summary> + internal class AccessTokenRefreshRequestC : AccessTokenRefreshRequest { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenRefreshRequestC"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal AccessTokenRefreshRequestC(AuthorizationServerDescription authorizationServer) + : base(authorizationServer.TokenEndpoint, authorizationServer.Version) { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationImplicitRequestC.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationImplicitRequestC.cs new file mode 100644 index 0000000..78bf48e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationImplicitRequestC.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationImplicitRequestC.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// A message sent by a web application Client to the AuthorizationServer + /// via the user agent to obtain authorization from the user and prepare + /// to issue an access token to the client if permission is granted. + /// </summary> + [Serializable] + internal class EndUserAuthorizationImplicitRequestC : EndUserAuthorizationImplicitRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationImplicitRequestC"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal EndUserAuthorizationImplicitRequestC(AuthorizationServerDescription authorizationServer) + : base(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationRequestC.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationRequestC.cs new file mode 100644 index 0000000..7c06897 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/Messages/EndUserAuthorizationRequestC.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// <copyright file="EndUserAuthorizationRequestC.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// A message sent by a web application Client to the AuthorizationServer + /// via the user agent to obtain authorization from the user and prepare + /// to issue an access token to the client if permission is granted. + /// </summary> + [Serializable] + internal class EndUserAuthorizationRequestC : EndUserAuthorizationRequest { + /// <summary> + /// Initializes a new instance of the <see cref="EndUserAuthorizationRequestC"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal EndUserAuthorizationRequestC(AuthorizationServerDescription authorizationServer) + : base(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { + Requires.NotNull(authorizationServer, "authorizationServer"); + Requires.True(authorizationServer.Version != null, "authorizationServer"); + Requires.True(authorizationServer.AuthorizationEndpoint != null, "authorizationServer"); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs index c29d167..edde2a9 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { + : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) { } /// <summary> @@ -38,12 +38,39 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier = null, string clientSecret = null) - : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientSecret) { + : this(authorizationEndpoint, tokenEndpoint, clientIdentifier, DefaultSecretApplicator(clientSecret)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationEndpoint">The authorization endpoint.</param> + /// <param name="tokenEndpoint">The token endpoint.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public UserAgentClient(Uri authorizationEndpoint, Uri tokenEndpoint, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : this(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint, TokenEndpoint = tokenEndpoint }, clientIdentifier, clientCredentialApplicator) { Requires.NotNull(authorizationEndpoint, "authorizationEndpoint"); Requires.NotNull(tokenEndpoint, "tokenEndpoint"); } /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationServer">The token issuer.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public UserAgentClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : base(authorizationServer, clientIdentifier, clientCredentialApplicator) { + } + + /// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. /// </summary> @@ -151,7 +178,7 @@ namespace DotNetOpenAuth.OAuth2 { authorization.Callback = new Uri("http://localhost/"); } - var request = implicitResponseType ? new EndUserAuthorizationImplicitRequest(this.AuthorizationServer) : new EndUserAuthorizationRequest(this.AuthorizationServer); + var request = implicitResponseType ? (EndUserAuthorizationRequest)new EndUserAuthorizationImplicitRequestC(this.AuthorizationServer) : new EndUserAuthorizationRequestC(this.AuthorizationServer); request.ClientIdentifier = this.ClientIdentifier; request.Callback = authorization.Callback; request.ClientState = state; diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs index 0429dcb..939d1df 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -26,7 +26,20 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="clientIdentifier">The client identifier.</param> /// <param name="clientSecret">The client secret.</param> public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null) - : base(authorizationServer, clientIdentifier, clientSecret) { + : this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="WebServerClient"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + /// <param name="clientIdentifier">The client identifier.</param> + /// <param name="clientCredentialApplicator"> + /// The tool to use to apply client credentials to authenticated requests to the Authorization Server. + /// May be <c>null</c> for clients with no secret or other means of authentication. + /// </param> + public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator) + : base(authorizationServer, clientIdentifier, clientCredentialApplicator) { } /// <summary> @@ -68,7 +81,7 @@ namespace DotNetOpenAuth.OAuth2 { public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization) { Requires.NotNull(authorization, "authorization"); Requires.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); - Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), ClientStrings.RequiredPropertyNotYetPreset, "ClientIdentifier"); + Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); if (authorization.Callback == null) { @@ -78,7 +91,7 @@ namespace DotNetOpenAuth.OAuth2 { authorization.SaveChanges(); } - var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { + var request = new EndUserAuthorizationRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, Callback = authorization.Callback, }; @@ -105,8 +118,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> /// <returns>The authorization state that contains the details of the authorization.</returns> public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) { - Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), ClientStrings.RequiredPropertyNotYetPreset, "ClientIdentifier"); - Requires.ValidState(!string.IsNullOrEmpty(this.ClientSecret), ClientStrings.RequiredPropertyNotYetPreset, "ClientSecret"); + Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); + Requires.ValidState(this.ClientCredentialApplicator != null, Strings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator"); if (request == null) { request = this.Channel.GetRequestFromContext(); diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj new file mode 100644 index 0000000..2a08dbf --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" /> + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{CCF3728A-B3D7-404A-9BC6-75197135F2D7}</ProjectGuid> + <AppDesignerFolder>Properties</AppDesignerFolder> + <AssemblyName>DotNetOpenAuth.OAuth2.ClientAuthorization</AssemblyName> + </PropertyGroup> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <ItemGroup> + <Compile Include="OAuth2\ChannelElements\EndUserAuthorizationResponseTypeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\GrantTypeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" /> + <Compile Include="OAuth2\ClientAuthorizationStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>ClientAuthorizationStrings.resx</DependentUpon> + </Compile> + <Compile Include="OAuth2\ClientType.cs" /> + <Compile Include="OAuth2\IClientDescription.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenClientCredentialsRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenFailedResponse.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRefreshRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenRequestBase.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenResult.cs" /> + <Compile Include="OAuth2\Messages\AccessTokenSuccessResponse.cs" /> + <Compile Include="OAuth2\Messages\AuthenticatedClientRequestBase.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationFailedResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationImplicitRequest.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationRequest.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationResponseType.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAccessTokenResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponse.cs" /> + <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessResponseBase.cs" /> + <Compile Include="OAuth2\Messages\GrantType.cs" /> + <Compile Include="OAuth2\Messages\IAccessTokenIssuingResponse.cs" /> + <Compile Include="OAuth2\Messages\IAccessTokenRequest.cs" /> + <Compile Include="OAuth2\Messages\IAccessTokenRequestInternal.cs" /> + <Compile Include="OAuth2\Messages\IMessageWithClientState.cs" /> + <Compile Include="OAuth2\Messages\ScopedAccessTokenRequest.cs" /> + <Compile Include="OAuth2\TokenEndpointProtocolException.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj"> + <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project> + <Name>DotNetOpenAuth.Core</Name> + </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2\DotNetOpenAuth.OAuth2.csproj"> + <Project>{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}</Project> + <Name>DotNetOpenAuth.OAuth2</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OAuth2\ClientAuthorizationStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>ClientAuthorizationStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <None Include="OAuth2\Messages\Access Token Request Messages.cd" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs index 2fba721..2fba721 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/GrantTypeEncoder.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/GrantTypeEncoder.cs index e0e8329..e0e8329 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/GrantTypeEncoder.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/GrantTypeEncoder.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ChannelBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs index 51ac58a..f2f674e 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ChannelBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ChannelElements/OAuth2ChannelBase.cs @@ -18,24 +18,6 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </summary> internal abstract class OAuth2ChannelBase : StandardMessageFactoryChannel { /// <summary> - /// The messages receivable by this channel. - /// </summary> - private static readonly Type[] MessageTypes = new Type[] { - typeof(AccessTokenRefreshRequest), - typeof(AccessTokenAuthorizationCodeRequest), - typeof(AccessTokenResourceOwnerPasswordCredentialsRequest), - typeof(AccessTokenClientCredentialsRequest), - typeof(AccessTokenSuccessResponse), - typeof(AccessTokenFailedResponse), - typeof(EndUserAuthorizationRequest), - typeof(EndUserAuthorizationImplicitRequest), - typeof(EndUserAuthorizationSuccessAuthCodeResponse), - typeof(EndUserAuthorizationSuccessAccessTokenResponse), - typeof(EndUserAuthorizationFailedResponse), - typeof(UnauthorizedResponse), - }; - - /// <summary> /// The protocol versions supported by this channel. /// </summary> private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray(); @@ -43,9 +25,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="OAuth2ChannelBase"/> class. /// </summary> - /// <param name="channelBindingElements">The channel binding elements.</param> - internal OAuth2ChannelBase(params IChannelBindingElement[] channelBindingElements) - : base(MessageTypes, Versions, channelBindingElements) { + /// <param name="messageTypes">The message types that are received by this channel.</param> + /// <param name="channelBindingElements"> + /// 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> + internal OAuth2ChannelBase(Type[] messageTypes, params IChannelBindingElement[] channelBindingElements) + : base(Requires.NotNull(messageTypes, "messageTypes"), Versions, channelBindingElements) { } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.Designer.cs new file mode 100644 index 0000000..e7e1b6b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.17614 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OAuth2 { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ClientAuthorizationStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ClientAuthorizationStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth2.ClientAuthorizationStrings", typeof(ClientAuthorizationStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to The request message type {0} should not be responded to with a refresh token.. + /// </summary> + internal static string RefreshTokenInappropriateForRequestType { + get { + return ResourceManager.GetString("RefreshTokenInappropriateForRequestType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The Authorization Server's token endpoint generated error {0}: '{1}'. + /// </summary> + internal static string TokenEndpointErrorFormat { + get { + return ResourceManager.GetString("TokenEndpointErrorFormat", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.resx b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.resx new file mode 100644 index 0000000..da2dd73 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientAuthorizationStrings.resx @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="RefreshTokenInappropriateForRequestType" xml:space="preserve"> + <value>The request message type {0} should not be responded to with a refresh token.</value> + </data> + <data name="TokenEndpointErrorFormat" xml:space="preserve"> + <value>The Authorization Server's token endpoint generated error {0}: '{1}'</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ClientType.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientType.cs index 9e8ed2a..9e8ed2a 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ClientType.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/ClientType.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IClientDescription.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/IClientDescription.cs index d30151b..b4bc689 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/IClientDescription.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/IClientDescription.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; /// <summary> /// A description of a client from an Authorization Server's point of view. @@ -15,11 +16,6 @@ namespace DotNetOpenAuth.OAuth2 { [ContractClass(typeof(IClientDescriptionContract))] public interface IClientDescription { /// <summary> - /// Gets the client secret. - /// </summary> - string Secret { get; } - - /// <summary> /// Gets the callback to use when an individual authorization request /// does not include an explicit callback URI. /// </summary> @@ -32,10 +28,15 @@ namespace DotNetOpenAuth.OAuth2 { ClientType ClientType { get; } /// <summary> + /// Gets a value indicating whether a non-empty secret is registered for this client. + /// </summary> + bool HasNonEmptySecret { get; } + + /// <summary> /// Determines whether a callback URI included in a client's authorization request /// is among those allowed callbacks for the registered client. /// </summary> - /// <param name="callback">The absolute URI the client has requested the authorization result be received at.</param> + /// <param name="callback">The absolute URI the client has requested the authorization result be received at. Never null.</param> /// <returns> /// <c>true</c> if the callback URL is allowable for this client; otherwise, <c>false</c>. /// </returns> @@ -56,6 +57,17 @@ namespace DotNetOpenAuth.OAuth2 { /// </para> /// </remarks> bool IsCallbackAllowed(Uri callback); + + /// <summary> + /// Checks whether the specified client secret is correct. + /// </summary> + /// <param name="secret">The secret obtained from the client.</param> + /// <returns><c>true</c> if the secret matches the one in the authorization server's record for the client; <c>false</c> otherwise.</returns> + /// <remarks> + /// All string equality checks, whether checking secrets or their hashes, + /// should be done using <see cref="MessagingUtilities.EqualsConstantTime"/> to mitigate timing attacks. + /// </remarks> + bool IsValidClientSecret(string secret); } /// <summary> @@ -66,14 +78,6 @@ namespace DotNetOpenAuth.OAuth2 { #region IClientDescription Members /// <summary> - /// Gets the client secret. - /// </summary> - /// <value></value> - string IClientDescription.Secret { - get { throw new NotImplementedException(); } - } - - /// <summary> /// Gets the type of the client. /// </summary> ClientType IClientDescription.ClientType { @@ -95,6 +99,13 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Gets a value indicating whether a non-empty secret is registered for this client. + /// </summary> + bool IClientDescription.HasNonEmptySecret { + get { throw new NotImplementedException(); } + } + + /// <summary> /// Determines whether a callback URI included in a client's authorization request /// is among those allowed callbacks for the registered client. /// </summary> @@ -108,6 +119,20 @@ namespace DotNetOpenAuth.OAuth2 { throw new NotImplementedException(); } + /// <summary> + /// Checks whether the specified client secret is correct. + /// </summary> + /// <param name="secret">The secret obtained from the client.</param> + /// <returns><c>true</c> if the secret matches the one in the authorization server's record for the client; <c>false</c> otherwise.</returns> + /// <remarks> + /// All string equality checks, whether checking secrets or their hashes, + /// should be done using <see cref="MessagingUtilities.EqualsConstantTime"/> to mitigate timing attacks. + /// </remarks> + bool IClientDescription.IsValidClientSecret(string secret) { + Requires.NotNullOrEmpty(secret, "secret"); + throw new NotImplementedException(); + } + #endregion } } diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/Access Token Request Messages.cd b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/Access Token Request Messages.cd new file mode 100644 index 0000000..5ec8629 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/Access Token Request Messages.cd @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1"> + <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenRequestBase" Collapsed="true"> + <Position X="3.75" Y="0.5" Width="2" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAAgAAAAAKAAAQAAAAAAAAAAAAAAAACAA=</HashCode> + <FileName>OAuth2\Messages\AccessTokenRequestBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OAuth2.Messages.ScopedAccessTokenRequest"> + <Position X="3" Y="2" Width="2" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAACAA=</HashCode> + <FileName>OAuth2\Messages\ScopedAccessTokenRequest.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenResourceOwnerPasswordCredentialsRequest" Collapsed="true"> + <Position X="0.5" Y="4.75" Width="4" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAgAAAIAAQAAAAAAECAQAAAAAABIA=</HashCode> + <FileName>OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenRefreshRequest" Collapsed="true"> + <Position X="5" Y="5" Width="2.25" /> + <TypeIdentifier> + <HashCode>AAAAAAAAQAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA=</HashCode> + <FileName>OAuth2\Messages\AccessTokenRefreshRequest.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenClientCredentialsRequest" Collapsed="true"> + <Position X="7.5" Y="5" Width="2.75" /> + <TypeIdentifier> + <HashCode>AAAAAAAAAAAAAAAAgAAAIAAQAAAAAAEAAAAAAAAABAA=</HashCode> + <FileName>OAuth2\Messages\AccessTokenClientCredentialsRequest.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenAuthorizationCodeRequest" Collapsed="true"> + <Position X="5.75" Y="2" Width="3" /> + <TypeIdentifier> + <HashCode>ACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAASAA=</HashCode> + <FileName>OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs</FileName> + </TypeIdentifier> + </Class> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs index 1f244f9..b8c9ede 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs @@ -18,51 +18,17 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// A request from a Client to an Authorization Server to exchange an authorization code for an access token, /// and (at the authorization server's option) a refresh token. /// </summary> - internal class AccessTokenAuthorizationCodeRequest : AccessTokenRequestBase, IAuthorizationCodeCarryingRequest { + internal class AccessTokenAuthorizationCodeRequest : AccessTokenRequestBase { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. /// </summary> /// <param name="tokenEndpoint">The Authorization Server's access token endpoint URL.</param> /// <param name="version">The version.</param> - internal AccessTokenAuthorizationCodeRequest(Uri tokenEndpoint, Version version) + protected AccessTokenAuthorizationCodeRequest(Uri tokenEndpoint, Version version) : base(tokenEndpoint, version) { } /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenAuthorizationCodeRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal AccessTokenAuthorizationCodeRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { - Requires.NotNull(authorizationServer, "authorizationServer"); - } - - #region IAuthorizationCodeCarryingRequest Members - - /// <summary> - /// Gets or sets the verification code or refresh/access token. - /// </summary> - /// <value>The code or token.</value> - string IAuthorizationCodeCarryingRequest.Code { - get { return this.AuthorizationCode; } - set { this.AuthorizationCode = value; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } - - /// <summary> - /// Gets the authorization that the code describes. - /// </summary> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { - get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } - } - - #endregion - - /// <summary> /// Gets the type of the grant. /// </summary> /// <value>The type of the grant.</value> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs index 48419eb..0eb9e7f 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs @@ -36,7 +36,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Gets the authorization that the code or token describes. /// </summary> IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { - get { return this.CredentialsValidated ? this : null; } + get { return this.ClientAuthenticated ? this : null; } } #endregion @@ -74,10 +74,5 @@ namespace DotNetOpenAuth.OAuth2.Messages { internal override GrantType GrantType { get { return Messages.GrantType.ClientCredentials; } } - - /// <summary> - /// Gets or sets a value indicating whether the resource owner's credentials have been validated at the authorization server. - /// </summary> - internal bool CredentialsValidated { get; set; } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenFailedResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs index 8c4b1c3..4aaf928 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenFailedResponse.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenFailedResponse.cs @@ -25,6 +25,11 @@ namespace DotNetOpenAuth.OAuth2.Messages { private readonly bool invalidClientCredentialsInAuthorizationHeader; /// <summary> + /// The headers to include in the response. + /// </summary> + private readonly WebHeaderCollection headers = new WebHeaderCollection(); + + /// <summary> /// Initializes a new instance of the <see cref="AccessTokenFailedResponse"/> class. /// </summary> /// <param name="request">The faulty request.</param> @@ -63,8 +68,8 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Gets the HTTP headers to add to the response. /// </summary> /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection IHttpDirectResponse.Headers { - get { return new WebHeaderCollection(); } + public WebHeaderCollection Headers { + get { return this.headers; } } #endregion diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRefreshRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRefreshRequest.cs index 2c3ab25..685f697 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRefreshRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRefreshRequest.cs @@ -14,50 +14,17 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// A request from the client to the token endpoint for a new access token /// in exchange for a refresh token that the client has previously obtained. /// </summary> - internal class AccessTokenRefreshRequest : ScopedAccessTokenRequest, IRefreshTokenCarryingRequest { + internal class AccessTokenRefreshRequest : ScopedAccessTokenRequest { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenRefreshRequest"/> class. /// </summary> /// <param name="tokenEndpoint">The token endpoint.</param> /// <param name="version">The version.</param> - internal AccessTokenRefreshRequest(Uri tokenEndpoint, Version version) + protected AccessTokenRefreshRequest(Uri tokenEndpoint, Version version) : base(tokenEndpoint, version) { } /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenRefreshRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal AccessTokenRefreshRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { - } - - #region IRefreshTokenCarryingRequest members - - /// <summary> - /// Gets or sets the verification code or refresh/access token. - /// </summary> - /// <value>The code or token.</value> - string IRefreshTokenCarryingRequest.RefreshToken { - get { return this.RefreshToken; } - set { this.RefreshToken = value; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - RefreshToken IRefreshTokenCarryingRequest.AuthorizationDescription { get; set; } - - /// <summary> - /// Gets the authorization that the token describes. - /// </summary> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { - get { return ((IRefreshTokenCarryingRequest)this).AuthorizationDescription; } - } - - #endregion - - /// <summary> /// Gets or sets the refresh token. /// </summary> /// <value>The refresh token.</value> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs index 7e41fcd..e6bbc34 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenRequestBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs @@ -16,7 +16,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <summary> /// A message sent from the client to the authorization server to exchange a previously obtained grant for an access token. /// </summary> - public abstract class AccessTokenRequestBase : AuthenticatedClientRequestBase, IAccessTokenRequest { + public abstract class AccessTokenRequestBase : AuthenticatedClientRequestBase, IAccessTokenRequestInternal { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenRequestBase"/> class. /// </summary> @@ -40,9 +40,12 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <value> /// Always true, because of our base class. /// </value> - bool IAccessTokenRequest.ClientAuthenticated { - get { return true; } - } + public bool ClientAuthenticated { get; internal set; } + + /// <summary> + /// Gets or sets the result of calling the authorization server host's access token creation method. + /// </summary> + AccessTokenResult IAccessTokenRequestInternal.AccessTokenResult { get; set; } /// <summary> /// Gets the type of the grant. diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs index 52e65be..52e65be 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs new file mode 100644 index 0000000..11e486b --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="AccessTokenResult.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + + /// <summary> + /// Describes the parameters to be fed into creating a response to an access token request. + /// </summary> + public class AccessTokenResult { + /// <summary> + /// Initializes a new instance of the <see cref="AccessTokenResult"/> class. + /// </summary> + /// <param name="accessToken">The access token to include in this result.</param> + public AccessTokenResult(AccessToken accessToken) { + Requires.NotNull(accessToken, "accessToken"); + this.AllowRefreshToken = true; + this.AccessToken = accessToken; + } + + /// <summary> + /// Gets or sets a value indicating whether to provide the client with a refresh token, when applicable. + /// </summary> + /// <value>The default value is <c>true</c>.</value> + /// <remarks>> + /// The refresh token will never be provided when this value is false. + /// The refresh token <em>may</em> be provided when this value is true. + /// </remarks> + public bool AllowRefreshToken { get; set; } + + /// <summary> + /// Gets the access token. + /// </summary> + public AccessToken AccessToken { get; private set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenSuccessResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenSuccessResponse.cs index 6d278c4..1de39a6 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenSuccessResponse.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenSuccessResponse.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <remarks> /// This message type is shared by the Web App, Rich App, and Username/Password profiles. /// </remarks> - internal class AccessTokenSuccessResponse : MessageBase, IHttpDirectResponse { + internal class AccessTokenSuccessResponse : MessageBase, IHttpDirectResponse, IAccessTokenIssuingResponse { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenSuccessResponse"/> class. /// </summary> @@ -92,6 +92,50 @@ namespace DotNetOpenAuth.OAuth2.Messages { [MessagePart(Protocol.scope, IsRequired = false, Encoder = typeof(ScopeEncoder))] public HashSet<string> Scope { get; private set; } + #region IAccessTokenIssuingResponse Members + + /// <summary> + /// Gets or sets the lifetime of the access token. + /// </summary> + /// <value> + /// The lifetime. + /// </value> + TimeSpan? IAccessTokenIssuingResponse.Lifetime { + get { return this.Lifetime; } + set { this.Lifetime = value; } + } + + #endregion + + #region IAuthorizationCarryingRequest + + /// <summary> + /// Gets the authorization that the token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return ((IAccessTokenCarryingRequest)this).AuthorizationDescription; } + } + + #endregion + + #region IAccessTokenCarryingRequest Members + + /// <summary> + /// Gets or sets the authorization that the token describes. + /// </summary> + /// <value></value> + AccessToken IAccessTokenCarryingRequest.AuthorizationDescription { get; set; } + + /// <summary> + /// Gets or sets the access token. + /// </summary> + string IAccessTokenCarryingRequest.AccessToken { + get { return this.AccessToken; } + set { this.AccessToken = value; } + } + + #endregion + /// <summary> /// Gets or sets a value indicating whether a refresh token is or should be included in the response. /// </summary> @@ -107,7 +151,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { // Per OAuth 2.0 section 4.4.3 (draft 23), refresh tokens should never be included // in a response to an access token request that used the client credential grant type. - ErrorUtilities.VerifyProtocol(!this.HasRefreshToken || !(this.OriginatingRequest is AccessTokenClientCredentialsRequest), OAuthStrings.RefreshTokenInappropriateForRequestType, this.OriginatingRequest.GetType().Name); + ErrorUtilities.VerifyProtocol(!this.HasRefreshToken || !(this.OriginatingRequest is AccessTokenClientCredentialsRequest), ClientAuthorizationStrings.RefreshTokenInappropriateForRequestType, this.OriginatingRequest.GetType().Name); } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AuthenticatedClientRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs index bc4d0ca..4631d83 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AuthenticatedClientRequestBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AuthenticatedClientRequestBase.cs @@ -6,13 +6,18 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System; - + using System.Net; using DotNetOpenAuth.Messaging; /// <summary> /// A direct message from the client to the authorization server that includes the client's credentials. /// </summary> - public abstract class AuthenticatedClientRequestBase : MessageBase { + public abstract class AuthenticatedClientRequestBase : MessageBase, IHttpDirectRequest { + /// <summary> + /// The backing for the <see cref="Headers"/> property. + /// </summary> + private readonly WebHeaderCollection headers = new WebHeaderCollection(); + /// <summary> /// Initializes a new instance of the <see cref="AuthenticatedClientRequestBase"/> class. /// </summary> @@ -26,7 +31,10 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Gets the client identifier previously obtained from the Authorization Server. /// </summary> /// <value>The client identifier.</value> - [MessagePart(Protocol.client_id, IsRequired = true)] + /// <remarks> + /// Not required, because the client id may be communicate through alternate means like HTTP Basic authentication (the OAuth 2 spec allows a lot of freedom here). + /// </remarks> + [MessagePart(Protocol.client_id, IsRequired = false)] public string ClientIdentifier { get; internal set; } /// <summary> @@ -38,5 +46,13 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </remarks> [MessagePart(Protocol.client_secret, IsRequired = false)] public string ClientSecret { get; internal set; } + + /// <summary> + /// Gets the HTTP headers of the request. + /// </summary> + /// <value>May be an empty collection, but must not be <c>null</c>.</value> + public WebHeaderCollection Headers { + get { return this.headers; } + } } }
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs index 7cc8e82..7cc8e82 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs index f5a5d67..4b662cd 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// to issue an access token to the client if permission is granted. /// </summary> [Serializable] - public class EndUserAuthorizationImplicitRequest : EndUserAuthorizationRequest, IAccessTokenRequest { + public class EndUserAuthorizationImplicitRequest : EndUserAuthorizationRequest, IAccessTokenRequestInternal { /// <summary> /// Gets or sets the grant type that the client expects of the authorization server. /// </summary> @@ -31,19 +31,11 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </summary> /// <param name="authorizationEndpoint">The Authorization Server's user authorization URL to direct the user to.</param> /// <param name="version">The protocol version.</param> - internal EndUserAuthorizationImplicitRequest(Uri authorizationEndpoint, Version version) + protected EndUserAuthorizationImplicitRequest(Uri authorizationEndpoint, Version version) : base(authorizationEndpoint, version) { } /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationImplicitRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal EndUserAuthorizationImplicitRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { - } - - /// <summary> /// Gets the grant type that the client expects of the authorization server. /// </summary> public override EndUserAuthorizationResponseType ResponseType { @@ -51,6 +43,11 @@ namespace DotNetOpenAuth.OAuth2.Messages { } /// <summary> + /// Gets or sets the result of calling the authorization server host's access token creation method. + /// </summary> + AccessTokenResult IAccessTokenRequestInternal.AccessTokenResult { get; set; } + + /// <summary> /// Gets a value indicating whether the client requesting the access token has authenticated itself. /// </summary> /// <value> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationRequest.cs index 45fa049..f229cf9 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationRequest.cs @@ -32,7 +32,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </summary> /// <param name="authorizationEndpoint">The Authorization Server's user authorization URL to direct the user to.</param> /// <param name="version">The protocol version.</param> - internal EndUserAuthorizationRequest(Uri authorizationEndpoint, Version version) + protected EndUserAuthorizationRequest(Uri authorizationEndpoint, Version version) : base(version, MessageTransport.Indirect, authorizationEndpoint) { Requires.NotNull(authorizationEndpoint, "authorizationEndpoint"); Requires.NotNull(version, "version"); @@ -41,17 +41,6 @@ namespace DotNetOpenAuth.OAuth2.Messages { } /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. - /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - internal EndUserAuthorizationRequest(AuthorizationServerDescription authorizationServer) - : this(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { - Requires.NotNull(authorizationServer, "authorizationServer"); - Requires.True(authorizationServer.Version != null, "authorizationServer"); - Requires.True(authorizationServer.AuthorizationEndpoint != null, "authorizationServer"); - } - - /// <summary> /// Gets the grant type that the client expects of the authorization server. /// </summary> public virtual EndUserAuthorizationResponseType ResponseType { diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationResponseType.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationResponseType.cs index 75ece0f..75ece0f 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationResponseType.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationResponseType.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs index 5c03e7a..7a79e46 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs @@ -19,7 +19,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// to indicate that user authorization was granted, carrying only an access token, /// and to return the user to the Client where they started their experience. /// </summary> - internal class EndUserAuthorizationSuccessAccessTokenResponse : EndUserAuthorizationSuccessResponseBase, IAccessTokenCarryingRequest, IHttpIndirectResponse { + internal class EndUserAuthorizationSuccessAccessTokenResponse : EndUserAuthorizationSuccessResponseBase, IAccessTokenIssuingResponse, IHttpIndirectResponse { /// <summary> /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAccessTokenResponse"/> class. /// </summary> @@ -75,6 +75,17 @@ namespace DotNetOpenAuth.OAuth2.Messages { #endregion /// <summary> + /// Gets or sets the lifetime of the access token. + /// </summary> + /// <value> + /// The lifetime. + /// </value> + TimeSpan? IAccessTokenIssuingResponse.Lifetime { + get { return this.Lifetime; } + set { this.Lifetime = value; } + } + + /// <summary> /// Gets or sets the token type. /// </summary> /// <value>Usually "bearer".</value> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs index dcacc14..9d6b015 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs @@ -16,7 +16,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// to indicate that user authorization was granted, carrying an authorization code and possibly an access token, /// and to return the user to the Client where they started their experience. /// </summary> - internal class EndUserAuthorizationSuccessAuthCodeResponse : EndUserAuthorizationSuccessResponseBase, IAuthorizationCodeCarryingRequest { + internal class EndUserAuthorizationSuccessAuthCodeResponse : EndUserAuthorizationSuccessResponseBase { /// <summary> /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessAuthCodeResponse"/> class. /// </summary> @@ -40,30 +40,6 @@ namespace DotNetOpenAuth.OAuth2.Messages { ((IMessageWithClientState)this).ClientState = request.ClientState; } - #region IAuthorizationCodeCarryingRequest Members - - /// <summary> - /// Gets or sets the authorization code. - /// </summary> - string IAuthorizationCodeCarryingRequest.Code { - get { return this.AuthorizationCode; } - set { this.AuthorizationCode = value; } - } - - /// <summary> - /// Gets or sets the authorization that the token describes. - /// </summary> - AuthorizationCode IAuthorizationCodeCarryingRequest.AuthorizationDescription { get; set; } - - /// <summary> - /// Gets the authorization that the code describes. - /// </summary> - IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { - get { return ((IAuthorizationCodeCarryingRequest)this).AuthorizationDescription; } - } - - #endregion - /// <summary> /// Gets or sets the authorization code. /// </summary> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs index ef0010e..ef0010e 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/GrantType.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/GrantType.cs index a26d405..a26d405 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/GrantType.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/GrantType.cs diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenIssuingResponse.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenIssuingResponse.cs new file mode 100644 index 0000000..1a54aca --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenIssuingResponse.cs @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------- +// <copyright file="IAccessTokenIssuingResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth2.ChannelElements; + + /// <summary> + /// A message sent from the Authorization Server to the client carrying an access token. + /// </summary> + internal interface IAccessTokenIssuingResponse : IAccessTokenCarryingRequest { + /// <summary> + /// Gets or sets the lifetime of the access token. + /// </summary> + /// <value>The lifetime.</value> + TimeSpan? Lifetime { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IAccessTokenRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs index 65378f9..65378f9 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IAccessTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs new file mode 100644 index 0000000..44af074 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="IAccessTokenRequestInternal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// Implemented by all message types whose response may contain an access token. + /// </summary> + public interface IAccessTokenRequestInternal : IAccessTokenRequest { + /// <summary> + /// Gets or sets the result of calling the authorization server host's access token creation method. + /// </summary> + AccessTokenResult AccessTokenResult { get; set; } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IMessageWithClientState.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IMessageWithClientState.cs index 71476f2..71476f2 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/IMessageWithClientState.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IMessageWithClientState.cs diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/ScopedAccessTokenRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/ScopedAccessTokenRequest.cs index 0ea6efb..0ea6efb 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/ScopedAccessTokenRequest.cs +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/ScopedAccessTokenRequest.cs diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs new file mode 100644 index 0000000..e86c27e --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/TokenEndpointProtocolException.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenEndpointProtocolException.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.Messages; + + /// <summary> + /// Describes an error generated by an Authorization Server's token endpoint. + /// </summary> + public class TokenEndpointProtocolException : ProtocolException { + /// <summary> + /// The message being processed that caused this exception to be thrown. + /// </summary> + private readonly AccessTokenRequestBase requestMessage; + + /// <summary> + /// The WWW-Authenticate header to add to the response message. + /// </summary> + private readonly string authenticateHeader; + + /// <summary> + /// Initializes a new instance of the <see cref="TokenEndpointProtocolException"/> class. + /// </summary> + /// <param name="requestMessage">The message whose processing resulted in this error.</param> + /// <param name="error">A single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>.</param> + /// <param name="description">A human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred.</param> + /// <param name="moreInformation">A URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error.</param> + /// <param name="authenticateHeader">The WWW-Authenticate header to add to the response.</param> + public TokenEndpointProtocolException(AccessTokenRequestBase requestMessage, string error, string description = null, Uri moreInformation = null, string authenticateHeader = null) + : base(string.Format(CultureInfo.CurrentCulture, ClientAuthorizationStrings.TokenEndpointErrorFormat, error, description)) { + Requires.NotNull(requestMessage, "requestMessage"); + Requires.NotNullOrEmpty(error, "error"); + + this.requestMessage = requestMessage; + this.Error = error; + this.Description = description; + this.MoreInformation = moreInformation; + this.authenticateHeader = authenticateHeader; + } + + /// <summary> + /// Initializes a new instance of the <see cref="TokenEndpointProtocolException"/> class. + /// </summary> + /// <param name="innerException">The inner exception.</param> + public TokenEndpointProtocolException(Exception innerException) + : base(Protocol.AccessTokenRequestErrorCodes.InvalidRequest, innerException) { + this.Error = Protocol.AccessTokenRequestErrorCodes.InvalidRequest; + } + + /// <summary> + /// Gets a single error code from <see cref="Protocol.AccessTokenRequestErrorCodes"/>. + /// </summary> + public string Error { get; private set; } + + /// <summary> + /// Gets a human-readable UTF-8 encoded text providing additional information, used to assist the client developer in understanding the error that occurred. + /// </summary> + public string Description { get; private set; } + + /// <summary> + /// Gets a URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error. + /// </summary> + public Uri MoreInformation { get; private set; } + + /// <summary> + /// Gets the response message to send to the client. + /// </summary> + /// <returns>A message.</returns> + public IDirectResponseProtocolMessage GetResponse() { + var response = this.requestMessage != null + ? new AccessTokenFailedResponse(this.requestMessage, this.authenticateHeader != null) + : new AccessTokenFailedResponse(); + response.Error = this.Error; + response.ErrorDescription = this.Description; + response.ErrorUri = this.MoreInformation; + if (this.authenticateHeader != null) { + response.Headers.Add(HttpRequestHeaders.WwwAuthenticate, this.authenticateHeader); + } + + return response; + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d536886 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Web.UI; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DotNetOpenAuth OAuth 2.0")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DotNetOpenAuth")] +[assembly: AssemblyCopyright("Copyright © 2011 Outercurve Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] + +[assembly: ContractVerification(true)] + +#if StrongNameSigned +// See comment at top of this file. We need this so that strong-naming doesn't +// keep this assembly from being useful to shared host (medium trust) web sites. +[assembly: AllowPartiallyTrustedCallers] + +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +#else +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +#endif diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs new file mode 100644 index 0000000..3e37018 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/Configuration/OAuth2ResourceServerSection.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2ResourceServerSection.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the <oauth2/resourceServer> section in the host's .config file. + /// </summary> + internal class OAuth2ResourceServerSection : ConfigurationElement { + /// <summary> + /// The name of the oauth2/client section. + /// </summary> + private const string SectionName = OAuth2SectionGroup.SectionName + "/resourceServer"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2ResourceServerSection"/> class. + /// </summary> + internal OAuth2ResourceServerSection() { + } + + /// <summary> + /// Gets the configuration section from the .config file. + /// </summary> + internal static OAuth2ResourceServerSection Configuration { + get { + Contract.Ensures(Contract.Result<OAuth2ResourceServerSection>() != null); + return (OAuth2ResourceServerSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ResourceServerSection(); + } + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj index 63806b8..eb54fee 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/DotNetOpenAuth.OAuth2.ResourceServer.csproj @@ -18,12 +18,16 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2ResourceServerSection.cs" /> + <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> + <Compile Include="OAuth2\IAccessTokenAnalyzer.cs" /> <Compile Include="OAuth2\ResourceServerStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>ResourceServerStrings.resx</DependentUpon> </Compile> <Compile Include="OAuth2\ResourceServer.cs" /> + <Compile Include="OAuth2\StandardAccessTokenAnalyzer.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs index 947c044..e9d596a 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ChannelElements/OAuth2ResourceServerChannel.cs @@ -52,8 +52,8 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { var fields = new Dictionary<string, string>(); string accessToken; if ((accessToken = SearchForBearerAccessTokenInRequest(request)) != null) { - fields["token_type"] = Protocol.AccessTokenTypes.Bearer; - fields["access_token"] = accessToken; + fields[Protocol.token_type] = Protocol.AccessTokenTypes.Bearer; + fields[Protocol.access_token] = accessToken; } if (fields.Count > 0) { @@ -101,7 +101,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { var webResponse = new OutgoingWebResponse(); - // The only direct response from a resource server is a 401 Unauthorized error. + // The only direct response from a resource server is some authorization error (400, 401, 403). var unauthorizedResponse = response as UnauthorizedResponse; ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected."); @@ -113,7 +113,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { // Now serialize all the message parts into the WWW-Authenticate header. var fields = this.MessageDescriptions.GetAccessor(response); - webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(Protocol.BearerHttpAuthorizationScheme, fields); + webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(unauthorizedResponse.Scheme, fields); return webResponse; } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs index 5aa1bb6..5c5a526 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/IAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/IAccessTokenAnalyzer.cs @@ -23,13 +23,10 @@ namespace DotNetOpenAuth.OAuth2 { /// Reads an access token to find out what data it authorizes access to. /// </summary> /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns>A value indicating whether this access token is valid.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope); + /// <param name="accessToken">The access token's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken); } /// <summary> @@ -47,17 +44,13 @@ namespace DotNetOpenAuth.OAuth2 { /// Reads an access token to find out what data it authorizes access to. /// </summary> /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns> - /// A value indicating whether this access token is valid. - /// </returns> - bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + /// <param name="accessToken">The access token's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + AccessToken IAccessTokenAnalyzer.DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) { Requires.NotNull(message, "message"); Requires.NotNullOrEmpty(accessToken, "accessToken"); - Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null)); - + Contract.Ensures(Contract.Result<AccessToken>() != null); throw new NotImplementedException(); } } diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs index 2830ab8..cd0fb55 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServer.cs @@ -26,6 +26,11 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> public class ResourceServer { /// <summary> + /// A reusable instance of the scope satisfied checker. + /// </summary> + private static readonly IScopeSatisfiedCheck DefaultScopeSatisfiedCheck = new StandardScopeSatisfiedCheck(); + + /// <summary> /// Initializes a new instance of the <see cref="ResourceServer"/> class. /// </summary> /// <param name="accessTokenAnalyzer">The access token analyzer.</param> @@ -34,6 +39,9 @@ namespace DotNetOpenAuth.OAuth2 { this.AccessTokenAnalyzer = accessTokenAnalyzer; this.Channel = new OAuth2ResourceServerChannel(); + this.ResourceOwnerPrincipalPrefix = string.Empty; + this.ClientPrincipalPrefix = "client:"; + this.ScopeSatisfiedCheck = DefaultScopeSatisfiedCheck; } /// <summary> @@ -43,59 +51,78 @@ namespace DotNetOpenAuth.OAuth2 { public IAccessTokenAnalyzer AccessTokenAnalyzer { get; private set; } /// <summary> - /// Gets the channel. + /// Gets or sets the service that checks whether a granted set of scopes satisfies a required set of scopes. /// </summary> - /// <value>The channel.</value> - internal OAuth2ResourceServerChannel Channel { get; private set; } + public IScopeSatisfiedCheck ScopeSatisfiedCheck { get; set; } /// <summary> - /// Discovers what access the client should have considering the access token in the current request. + /// Gets or sets the prefix to apply to a resource owner's username when used as the username in an <see cref="IPrincipal"/>. /// </summary> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> - /// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) { - return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope); - } + /// <value>The default value is the empty string.</value> + public string ResourceOwnerPrincipalPrefix { get; set; } + + /// <summary> + /// Gets or sets the prefix to apply to a client identifier when used as the username in an <see cref="IPrincipal"/>. + /// </summary> + /// <value>The default value is "client:"</value> + public string ClientPrincipalPrefix { get; set; } + + /// <summary> + /// Gets the channel. + /// </summary> + /// <value>The channel.</value> + internal OAuth2ResourceServerChannel Channel { get; private set; } /// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="userName">The name on the account the client has access to.</param> - /// <param name="scope">The set of operations the client is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The access token describing the authorization the client has. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out string userName, out HashSet<string> scope) { - Requires.NotNull(httpRequestInfo, "httpRequestInfo"); + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual AccessToken GetAccessToken(HttpRequestBase httpRequestInfo = null, params string[] requiredScopes) { + Requires.NotNull(requiredScopes, "requiredScopes"); + Requires.ValidState(this.ScopeSatisfiedCheck != null, Strings.RequiredPropertyNotYetPreset); + if (httpRequestInfo == null) { + httpRequestInfo = this.Channel.GetRequestFromContext(); + } + AccessToken accessToken; AccessProtectedResourceRequest request = null; try { if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) { - if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) { - // No errors to return. - return null; + accessToken = this.AccessTokenAnalyzer.DeserializeAccessToken(request, request.AccessToken); + ErrorUtilities.VerifyHost(accessToken != null, "IAccessTokenAnalyzer.DeserializeAccessToken returned a null reslut."); + if (string.IsNullOrEmpty(accessToken.User) && string.IsNullOrEmpty(accessToken.ClientIdentifier)) { + Logger.OAuth.Error("Access token rejected because both the username and client id properties were null or empty."); + ErrorUtilities.ThrowProtocol(ResourceServerStrings.InvalidAccessToken); } - throw ErrorUtilities.ThrowProtocol(ResourceServerStrings.InvalidAccessToken); - } else { - var response = new UnauthorizedResponse(new ProtocolException(ResourceServerStrings.MissingAccessToken)); + var requiredScopesSet = OAuthUtilities.ParseScopeSet(requiredScopes); + if (!this.ScopeSatisfiedCheck.IsScopeSatisfied(requiredScope: requiredScopesSet, grantedScope: accessToken.Scope)) { + var response = UnauthorizedResponse.InsufficientScope(request, requiredScopesSet); + throw new ProtocolFaultResponseException(this.Channel, response); + } - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); + return accessToken; + } else { + var ex = new ProtocolException(ResourceServerStrings.MissingAccessToken); + var response = UnauthorizedResponse.InvalidRequest(ex); + throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } } catch (ProtocolException ex) { - var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); + if (ex is ProtocolFaultResponseException) { + // This doesn't need to be wrapped again. + throw; + } - userName = null; - scope = null; - return this.Channel.PrepareResponse(response); + var response = request != null ? UnauthorizedResponse.InvalidToken(request, ex) : UnauthorizedResponse.InvalidRequest(ex); + throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } } @@ -103,17 +130,29 @@ namespace DotNetOpenAuth.OAuth2 { /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The principal that contains the user and roles that the access token is authorized for. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestBase httpRequestInfo, out IPrincipal principal) { - string username; - HashSet<string> scope; - var result = this.VerifyAccess(httpRequestInfo, out username, out scope); - principal = result == null ? new OAuthPrincipal(username, scope != null ? scope.ToArray() : new string[0]) : null; - return result; + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual IPrincipal GetPrincipal(HttpRequestBase httpRequestInfo = null, params string[] requiredScopes) { + AccessToken accessToken = this.GetAccessToken(httpRequestInfo, requiredScopes); + + // Mitigates attacks on this approach of differentiating clients from resource owners + // by checking that a username doesn't look suspiciously engineered to appear like the other type. + ErrorUtilities.VerifyProtocol(accessToken.User == null || string.IsNullOrEmpty(this.ClientPrincipalPrefix) || !accessToken.User.StartsWith(this.ClientPrincipalPrefix, StringComparison.OrdinalIgnoreCase), ResourceServerStrings.ResourceOwnerNameLooksLikeClientIdentifier); + ErrorUtilities.VerifyProtocol(accessToken.ClientIdentifier == null || string.IsNullOrEmpty(this.ResourceOwnerPrincipalPrefix) || !accessToken.ClientIdentifier.StartsWith(this.ResourceOwnerPrincipalPrefix, StringComparison.OrdinalIgnoreCase), ResourceServerStrings.ClientIdentifierLooksLikeResourceOwnerName); + + string principalUserName = !string.IsNullOrEmpty(accessToken.User) + ? this.ResourceOwnerPrincipalPrefix + accessToken.User + : this.ClientPrincipalPrefix + accessToken.ClientIdentifier; + string[] principalScope = accessToken.Scope != null ? accessToken.Scope.ToArray() : new string[0]; + var principal = new OAuthPrincipal(principalUserName, principalScope); + + return principal; } /// <summary> @@ -121,17 +160,19 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">HTTP details from an incoming WCF message.</param> /// <param name="requestUri">The URI of the WCF service endpoint.</param> - /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> - /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// The principal that contains the user and roles that the access token is authorized for. Never <c>null</c>. /// </returns> - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] - public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { + /// <exception cref="ProtocolFaultResponseException"> + /// Thrown when the client is not authorized. This exception should be caught and the + /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. + /// </exception> + public virtual IPrincipal GetPrincipal(HttpRequestMessageProperty request, Uri requestUri, params string[] requiredScopes) { Requires.NotNull(request, "request"); Requires.NotNull(requestUri, "requestUri"); - return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); + return this.GetPrincipal(new HttpRequestInfo(request, requestUri), requiredScopes); } } } diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs index 606b072..f97b41b 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.Designer.cs @@ -61,6 +61,15 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Looks up a localized string similar to Client Identifier starts with a resource owner prefix. Authorization aborted.. + /// </summary> + internal static string ClientIdentifierLooksLikeResourceOwnerName { + get { + return ResourceManager.GetString("ClientIdentifierLooksLikeResourceOwnerName", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Invalid access token.. /// </summary> internal static string InvalidAccessToken { @@ -77,5 +86,14 @@ namespace DotNetOpenAuth.OAuth2 { return ResourceManager.GetString("MissingAccessToken", resourceCulture); } } + + /// <summary> + /// Looks up a localized string similar to Resource owner username starts with a client prefix. Authorization aborted.. + /// </summary> + internal static string ResourceOwnerNameLooksLikeClientIdentifier { + get { + return ResourceManager.GetString("ResourceOwnerNameLooksLikeClientIdentifier", resourceCulture); + } + } } } diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx index 175a386..46943c4 100644 --- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/ResourceServerStrings.resx @@ -117,10 +117,16 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> + <data name="ClientIdentifierLooksLikeResourceOwnerName" xml:space="preserve"> + <value>Client Identifier starts with a resource owner prefix. Authorization aborted.</value> + </data> <data name="InvalidAccessToken" xml:space="preserve"> <value>Invalid access token.</value> </data> <data name="MissingAccessToken" xml:space="preserve"> <value>Missing access token.</value> </data> + <data name="ResourceOwnerNameLooksLikeClientIdentifier" xml:space="preserve"> + <value>Resource owner username starts with a client prefix. Authorization aborted.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs index 636f490..54d86ff 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs @@ -45,22 +45,14 @@ namespace DotNetOpenAuth.OAuth2 { /// Reads an access token to find out what data it authorizes access to. /// </summary> /// <param name="message">The message carrying the access token.</param> - /// <param name="accessToken">The access token.</param> - /// <param name="user">The user whose data is accessible with this access token.</param> - /// <param name="scope">The scope of access authorized by this access token.</param> - /// <returns> - /// A value indicating whether this access token is valid. - /// </returns> - /// <remarks> - /// This method also responsible to throw a <see cref="ProtocolException"/> or return - /// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server. - /// </remarks> - public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { + /// <param name="accessToken">The access token's serialized representation.</param> + /// <returns>The deserialized, validated token.</returns> + /// <exception cref="ProtocolException">Thrown if the access token is expired, invalid, or from an untrusted authorization server.</exception> + public virtual AccessToken DeserializeAccessToken(IDirectedProtocolMessage message, string accessToken) { var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); - var token = accessTokenFormatter.Deserialize(message, accessToken, Protocol.access_token); - user = token.User; - scope = new HashSet<string>(token.Scope, OAuthUtilities.ScopeStringComparer); - return true; + var token = new AccessToken(); + accessTokenFormatter.Deserialize(token, message, accessToken, Protocol.access_token); + return token; } } } diff --git a/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs new file mode 100644 index 0000000..112e756 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/Configuration/OAuth2SectionGroup.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuth2SectionGroup.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using System.Diagnostics.Contracts; + + /// <summary> + /// Represents the <oauth> element in the host's .config file. + /// </summary> + internal class OAuth2SectionGroup : ConfigurationSectionGroup { + /// <summary> + /// The name of the oauth section. + /// </summary> + internal const string SectionName = DotNetOpenAuthSection.SectionName + "/oauth2"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuth2SectionGroup"/> class. + /// </summary> + internal OAuth2SectionGroup() { + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj index 438a21d..696d8a9 100644 --- a/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj +++ b/src/DotNetOpenAuth.OAuth2/DotNetOpenAuth.OAuth2.csproj @@ -18,72 +18,30 @@ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> </PropertyGroup> <ItemGroup> + <Compile Include="Configuration\OAuth2SectionGroup.cs" /> <Compile Include="GlobalSuppressions.cs" /> - <Compile Include="OAuth2\AuthorizationState.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessRequestBindingElement.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessToken.cs" /> - <Compile Include="OAuth2\ChannelElements\AccessTokenBindingElement.cs" /> + <Compile Include="OAuth2\AccessToken.cs" /> <Compile Include="OAuth2\ChannelElements\AuthorizationDataBag.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthServerBindingElementBase.cs" /> - <Compile Include="OAuth2\ChannelElements\GrantTypeEncoder.cs" /> - <Compile Include="OAuth2\ChannelElements\EndUserAuthorizationResponseTypeEncoder.cs" /> + <Compile Include="OAuth2\ChannelElements\ClientAuthenticationResult.cs" /> <Compile Include="OAuth2\ChannelElements\IAccessTokenCarryingRequest.cs" /> - <Compile Include="OAuth2\ChannelElements\IAuthorizationCodeCarryingRequest.cs" /> - <Compile Include="OAuth2\ChannelElements\IOAuth2ChannelWithAuthorizationServer.cs" /> - <Compile Include="OAuth2\ChannelElements\IRefreshTokenCarryingRequest.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" /> + <Compile Include="OAuth2\IScopeSatisfiedCheck.cs" /> <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" /> <Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" /> <Compile Include="OAuth2\ChannelElements\IAuthorizationCarryingRequest.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> - <Compile Include="OAuth2\ChannelElements\RefreshToken.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthorizationCode.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthorizationCodeBindingElement.cs" /> - <Compile Include="OAuth2\ChannelElements\AuthServerAllFlowsBindingElement.cs" /> - <Compile Include="OAuth2\ClientDescription.cs" /> - <Compile Include="OAuth2\ClientType.cs" /> - <Compile Include="OAuth2\IAccessTokenAnalyzer.cs" /> - <Compile Include="OAuth2\IAuthorizationServer.cs" /> - <Compile Include="OAuth2\IAuthorizationState.cs" /> - <Compile Include="OAuth2\IClientAuthorizationTracker.cs" /> - <Compile Include="OAuth2\IClientDescription.cs" /> <Compile Include="OAuth2\Messages\AccessProtectedResourceRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenRequestBase.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenClientCredentialsRequest.cs" /> - <Compile Include="OAuth2\Messages\AuthenticatedClientRequestBase.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationImplicitRequest.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAccessTokenResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationFailedResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponse.cs" /> - <Compile Include="OAuth2\Messages\GrantType.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenRefreshRequest.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationResponseType.cs" /> - <Compile Include="OAuth2\Messages\IAccessTokenRequest.cs" /> - <Compile Include="OAuth2\Messages\IMessageWithClientState.cs" /> - <Compile Include="OAuth2\Messages\ScopedAccessTokenRequest.cs" /> <Compile Include="OAuth2\Messages\UnauthorizedResponse.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenFailedResponse.cs" /> - <Compile Include="OAuth2\Messages\AccessTokenSuccessResponse.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessResponseBase.cs" /> - <Compile Include="OAuth2\StandardAccessTokenAnalyzer.cs" /> <Compile Include="OAuth2\OAuthUtilities.cs" /> + <Compile Include="OAuth2\StandardScopeSatisfiedCheck.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="OAuth2\ChannelElements\OAuth2AuthorizationServerChannel.cs" /> <Compile Include="OAuth2\Messages\MessageBase.cs" /> - <Compile Include="OAuth2\Messages\EndUserAuthorizationRequest.cs" /> <Compile Include="OAuth2\Protocol.cs" /> <Compile Include="OAuth2\OAuthStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>OAuthStrings.resx</DependentUpon> </Compile> - <Compile Include="OAuth2\AuthorizationServerDescription.cs" /> </ItemGroup> <ItemGroup> - <None Include="OAuth2\Messages\OAuth 2 Messages.cd" /> <None Include="OAuth2\OAuth 2 client facades.cd" /> </ItemGroup> <ItemGroup> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/AccessToken.cs index 84b17cc..5890d93 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessToken.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/AccessToken.cs @@ -4,18 +4,19 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OAuth2.ChannelElements { +namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Security.Cryptography; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth2.ChannelElements; /// <summary> /// A short-lived token that accompanies HTTP requests to protected data to authorize the request. /// </summary> - internal class AccessToken : AuthorizationDataBag { + public class AccessToken : AuthorizationDataBag { /// <summary> /// Initializes a new instance of the <see cref="AccessToken"/> class. /// </summary> @@ -23,31 +24,65 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// Gets or sets the lifetime of the access token. + /// </summary> + /// <value>The lifetime.</value> + [MessagePart(Encoder = typeof(TimespanSecondsEncoder))] + public TimeSpan? Lifetime { get; set; } + + /// <summary> + /// Gets the type of this instance. /// </summary> - /// <param name="authorization">The authorization to be described by the access token.</param> - /// <param name="lifetime">The lifetime of the access token.</param> - internal AccessToken(IAuthorizationDescription authorization, TimeSpan? lifetime) { + /// <value>The type of the bag.</value> + /// <remarks> + /// This ensures that one token cannot be misused as another kind of token. + /// </remarks> + protected override Type BagType { + get { + // different roles (authorization server vs. Client) may derive from AccessToken, but they are all interoperable. + return typeof(AccessToken); + } + } + + /// <summary> + /// Creates a formatter capable of serializing/deserializing an access token. + /// </summary> + /// <param name="signingKey">The crypto service provider with the authorization server's private key used to asymmetrically sign the access token.</param> + /// <param name="encryptingKey">The crypto service provider with the resource server's public key used to encrypt the access token.</param> + /// <returns>An access token serializer.</returns> + internal static IDataBagFormatter<AccessToken> CreateFormatter(RSACryptoServiceProvider signingKey, RSACryptoServiceProvider encryptingKey) { + Contract.Requires(signingKey != null || !signingKey.PublicOnly); + Contract.Requires(encryptingKey != null); + Contract.Ensures(Contract.Result<IDataBagFormatter<AccessToken>>() != null); + + return new UriStyleMessageFormatter<AccessToken>(signingKey, encryptingKey); + } + + /// <summary> + /// Initializes this instance of the <see cref="AccessToken"/> class. + /// </summary> + /// <param name="authorization">The authorization to apply to this access token.</param> + internal void ApplyAuthorization(IAuthorizationDescription authorization) { Requires.NotNull(authorization, "authorization"); this.ClientIdentifier = authorization.ClientIdentifier; this.UtcCreationDate = authorization.UtcIssued; this.User = authorization.User; this.Scope.ResetContents(authorization.Scope); - this.Lifetime = lifetime; } /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// Initializes this instance of the <see cref="AccessToken"/> class. /// </summary> - /// <param name="clientIdentifier">The client identifier.</param> /// <param name="scopes">The scopes.</param> /// <param name="username">The username of the account that authorized this token.</param> /// <param name="lifetime">The lifetime for this access token.</param> - internal AccessToken(string clientIdentifier, IEnumerable<string> scopes, string username, TimeSpan? lifetime) { - Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); - - this.ClientIdentifier = clientIdentifier; + /// <remarks> + /// The <see cref="AuthorizationDataBag.ClientIdentifier"/> is left <c>null</c> in this case because this constructor + /// is invoked in the case where the client is <em>not</em> authenticated, and therefore no + /// trust in the client_id is appropriate. + /// </remarks> + internal void ApplyAuthorization(IEnumerable<string> scopes, string username, TimeSpan? lifetime) { this.Scope.ResetContents(scopes); this.User = username; this.Lifetime = lifetime; @@ -55,24 +90,12 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } /// <summary> - /// Gets or sets the lifetime of the access token. - /// </summary> - /// <value>The lifetime.</value> - [MessagePart(Encoder = typeof(TimespanSecondsEncoder))] - internal TimeSpan? Lifetime { get; set; } - - /// <summary> - /// Creates a formatter capable of serializing/deserializing an access token. + /// Serializes this instance to a simple string for transmission to the client. /// </summary> - /// <param name="signingKey">The crypto service provider with the authorization server's private key used to asymmetrically sign the access token.</param> - /// <param name="encryptingKey">The crypto service provider with the resource server's public key used to encrypt the access token.</param> - /// <returns>An access token serializer.</returns> - internal static IDataBagFormatter<AccessToken> CreateFormatter(RSACryptoServiceProvider signingKey, RSACryptoServiceProvider encryptingKey) { - Contract.Requires(signingKey != null || !signingKey.PublicOnly); - Contract.Requires(encryptingKey != null); - Contract.Ensures(Contract.Result<IDataBagFormatter<AccessToken>>() != null); - - return new UriStyleMessageFormatter<AccessToken>(signingKey, encryptingKey); + /// <returns>A non-empty string.</returns> + protected internal virtual string Serialize() { + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + throw new NotSupportedException(); } /// <summary> diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs deleted file mode 100644 index 7a68060..0000000 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ /dev/null @@ -1,183 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AccessRequestBindingElement.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// Decodes verification codes, refresh tokens and access tokens on incoming messages. - /// </summary> - /// <remarks> - /// This binding element also ensures that the code/token coming in is issued to - /// the same client that is sending the code/token and that the authorization has - /// not been revoked and that an access token has not expired. - /// </remarks> - internal class AccessRequestBindingElement : AuthServerBindingElementBase { - /// <summary> - /// Initializes a new instance of the <see cref="AccessRequestBindingElement"/> class. - /// </summary> - internal AccessRequestBindingElement() { - } - - /// <summary> - /// Gets the protection commonly offered (if any) by this binding element. - /// </summary> - /// <value></value> - /// <remarks> - /// This value is used to assist in sorting binding elements in the channel stack. - /// </remarks> - public override 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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; - if (authCodeCarrier != null) { - var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); - var code = authCodeCarrier.AuthorizationDescription; - authCodeCarrier.Code = codeFormatter.Serialize(code); - return MessageProtections.None; - } - - var accessTokenCarrier = message as IAccessTokenCarryingRequest; - if (accessTokenCarrier != null) { - var responseWithOriginatingRequest = (IDirectResponseProtocolMessage)message; - var request = (IAccessTokenRequest)responseWithOriginatingRequest.OriginatingRequest; - - using (var resourceServerKey = this.AuthorizationServer.GetResourceServerEncryptionKey(request)) { - var tokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServer.AccessTokenSigningKey, resourceServerKey); - var token = accessTokenCarrier.AuthorizationDescription; - accessTokenCarrier.AccessToken = tokenFormatter.Serialize(token); - } - - return MessageProtections.None; - } - - var accessTokenResponse = message as AccessTokenSuccessResponse; - if (accessTokenResponse != null) { - var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse; - var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest; - ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.ClientCredentials || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken); - } - - 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> - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "unauthorizedclient", Justification = "Protocol requirement")] - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "incorrectclientcredentials", Justification = "Protocol requirement")] - [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "authorizationexpired", Justification = "Protocol requirement")] - [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "DotNetOpenAuth.Messaging.ErrorUtilities.VerifyProtocol(System.Boolean,System.String,System.Object[])", Justification = "Protocol requirement")] - public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var tokenRequest = message as IAuthorizationCarryingRequest; - if (tokenRequest != null) { - try { - var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; - var refreshTokenCarrier = message as IRefreshTokenCarryingRequest; - var resourceOwnerPasswordCarrier = message as AccessTokenResourceOwnerPasswordCredentialsRequest; - var clientCredentialOnly = message as AccessTokenClientCredentialsRequest; - if (authCodeCarrier != null) { - var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); - var authorizationCode = authorizationCodeFormatter.Deserialize(message, authCodeCarrier.Code, Protocol.code); - authCodeCarrier.AuthorizationDescription = authorizationCode; - } else if (refreshTokenCarrier != null) { - var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); - var refreshToken = refreshTokenFormatter.Deserialize(message, refreshTokenCarrier.RefreshToken, Protocol.refresh_token); - refreshTokenCarrier.AuthorizationDescription = refreshToken; - } else if (resourceOwnerPasswordCarrier != null) { - try { - if (this.AuthorizationServer.IsResourceOwnerCredentialValid(resourceOwnerPasswordCarrier.UserName, resourceOwnerPasswordCarrier.Password)) { - resourceOwnerPasswordCarrier.CredentialsValidated = true; - } else { - Logger.OAuth.WarnFormat( - "Resource owner password credential for user \"{0}\" rejected by authorization server host.", - resourceOwnerPasswordCarrier.UserName); - - // TODO: fix this to report the appropriate error code for a bad credential. - throw new ProtocolException(); - } - } catch (NotSupportedException) { - // TODO: fix this to return the appropriate error code for not supporting resource owner password credentials - throw new ProtocolException(); - } catch (NotImplementedException) { - // TODO: fix this to return the appropriate error code for not supporting resource owner password credentials - throw new ProtocolException(); - } - } else if (clientCredentialOnly != null) { - // this method will throw later if the credentials are false. - clientCredentialOnly.CredentialsValidated = true; - } else { - throw ErrorUtilities.ThrowInternal("Unexpected message type: " + tokenRequest.GetType()); - } - } catch (ExpiredMessageException ex) { - throw ErrorUtilities.Wrap(ex, Protocol.authorization_expired); - } - - var accessRequest = tokenRequest as AccessTokenRequestBase; - if (accessRequest != null) { - // Make sure the client sending us this token is the client we issued the token to. - ErrorUtilities.VerifyProtocol(string.Equals(accessRequest.ClientIdentifier, tokenRequest.AuthorizationDescription.ClientIdentifier, StringComparison.Ordinal), Protocol.incorrect_client_credentials); - - // Check that the client secret is correct. - var client = this.AuthorizationServer.GetClientOrThrow(accessRequest.ClientIdentifier); - string secret = client.Secret; - ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(secret), Protocol.unauthorized_client); // an empty secret is not allowed for client authenticated calls. - ErrorUtilities.VerifyProtocol(MessagingUtilities.EqualsConstantTime(secret, accessRequest.ClientSecret), Protocol.incorrect_client_credentials); - - var scopedAccessRequest = accessRequest as ScopedAccessTokenRequest; - if (scopedAccessRequest != null) { - // Make sure the scope the client is requesting does not exceed the scope in the grant. - ErrorUtilities.VerifyProtocol(scopedAccessRequest.Scope.IsSubsetOf(tokenRequest.AuthorizationDescription.Scope), OAuthStrings.AccessScopeExceedsGrantScope, scopedAccessRequest.Scope, tokenRequest.AuthorizationDescription.Scope); - } - } - - // Make sure the authorization this token represents hasn't already been revoked. - ErrorUtilities.VerifyProtocol(this.AuthorizationServer.IsAuthorizationValid(tokenRequest.AuthorizationDescription), Protocol.authorization_expired); - - return MessageProtections.None; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessTokenBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessTokenBindingElement.cs deleted file mode 100644 index 4c63f29..0000000 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessTokenBindingElement.cs +++ /dev/null @@ -1,93 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AccessTokenBindingElement.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OAuth2.Messages; - - /// <summary> - /// Serializes access tokens inside an outgoing message. - /// </summary> - internal class AccessTokenBindingElement : AuthServerBindingElementBase { - /// <summary> - /// Initializes a new instance of the <see cref="AccessTokenBindingElement"/> class. - /// </summary> - internal AccessTokenBindingElement() { - } - - /// <summary> - /// Gets the protection commonly offered (if any) by this binding element. - /// </summary> - /// <value>Always <c>MessageProtections.None</c></value> - /// <remarks> - /// This value is used to assist in sorting binding elements in the channel stack. - /// </remarks> - public override 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> - public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var directResponse = message as IDirectResponseProtocolMessage; - IAccessTokenRequest request = directResponse != null ? directResponse.OriginatingRequest as IAccessTokenRequest : null; - - var implicitGrantResponse = message as EndUserAuthorizationSuccessAccessTokenResponse; - if (implicitGrantResponse != null) { - IAccessTokenCarryingRequest tokenCarryingResponse = implicitGrantResponse; - tokenCarryingResponse.AuthorizationDescription = new AccessToken(request.ClientIdentifier, implicitGrantResponse.Scope, implicitGrantResponse.AuthorizingUsername, implicitGrantResponse.Lifetime); - - return MessageProtections.None; - } - - var accessTokenResponse = message as AccessTokenSuccessResponse; - if (accessTokenResponse != null) { - var authCarryingRequest = (IAuthorizationCarryingRequest)request; - var accessToken = new AccessToken(authCarryingRequest.AuthorizationDescription, accessTokenResponse.Lifetime); - using (var resourceServerEncryptionKey = this.AuthorizationServer.GetResourceServerEncryptionKey(request)) { - var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServer.AccessTokenSigningKey, resourceServerEncryptionKey); - accessTokenResponse.AccessToken = accessTokenFormatter.Serialize(accessToken); - } - - if (accessTokenResponse.HasRefreshToken) { - var refreshToken = new RefreshToken(authCarryingRequest.AuthorizationDescription); - var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); - accessTokenResponse.RefreshToken = refreshTokenFormatter.Serialize(refreshToken); - } - } - - 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> - public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - return null; - } - } -} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs deleted file mode 100644 index 24ac020..0000000 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthServerAllFlowsBindingElement.cs +++ /dev/null @@ -1,83 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthServerAllFlowsBindingElement.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using System.Linq; - using System.Text; - using DotNetOpenAuth.OAuth2.Messages; - using Messaging; - - /// <summary> - /// A binding element that should be applied for authorization server channels regardless of which flows - /// are supported. - /// </summary> - internal class AuthServerAllFlowsBindingElement : AuthServerBindingElementBase { - /// <summary> - /// Initializes a new instance of the <see cref="AuthServerAllFlowsBindingElement"/> class. - /// </summary> - internal AuthServerAllFlowsBindingElement() { - } - - /// <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 override 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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - 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 override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var authorizationRequest = message as EndUserAuthorizationRequest; - if (authorizationRequest != null) { - var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier); - ErrorUtilities.VerifyProtocol(authorizationRequest.Callback == null || client.IsCallbackAllowed(authorizationRequest.Callback), OAuthStrings.ClientCallbackDisallowed, authorizationRequest.Callback); - ErrorUtilities.VerifyProtocol(authorizationRequest.Callback != null || client.DefaultCallback != null, OAuthStrings.NoCallback); - - return MessageProtections.None; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs deleted file mode 100644 index d602cae..0000000 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationCodeBindingElement.cs +++ /dev/null @@ -1,101 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AuthorizationCodeBindingElement.cs" company="Outercurve Foundation"> -// Copyright (c) Outercurve Foundation. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth2.ChannelElements { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using Messages; - using Messaging; - using Messaging.Bindings; - - /// <summary> - /// A binding element for OAuth 2.0 authorization servers that create/verify - /// issued authorization codes as part of obtaining access/refresh tokens. - /// </summary> - internal class AuthorizationCodeBindingElement : AuthServerBindingElementBase { - /// <summary> - /// Initializes a new instance of the <see cref="AuthorizationCodeBindingElement"/> class. - /// </summary> - internal AuthorizationCodeBindingElement() { - } - - /// <summary> - /// Gets the protection commonly offered (if any) by this binding element. - /// </summary> - /// <value>Always <c>MessageProtections.None</c></value> - /// <remarks> - /// This value is used to assist in sorting binding elements in the channel stack. - /// </remarks> - public override MessageProtections Protection { - get { return MessageProtections.None; } - } - - /// <summary> - /// Gets the maximum message age from the standard expiration binding element. - /// </summary> - /// <value>This interval need not account for clock skew because it is only compared within a single authorization server or farm of servers.</value> - internal static TimeSpan MaximumMessageAge { - get { return Configuration.DotNetOpenAuthSection.Messaging.MaximumMessageLifetimeNoSkew; } - } - - /// <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 override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var response = message as EndUserAuthorizationSuccessAuthCodeResponse; - if (response != null) { - var directResponse = (IDirectResponseProtocolMessage)response; - var request = (EndUserAuthorizationRequest)directResponse.OriginatingRequest; - IAuthorizationCodeCarryingRequest tokenCarryingResponse = response; - tokenCarryingResponse.AuthorizationDescription = new AuthorizationCode(request.ClientIdentifier, request.Callback, response.Scope, response.AuthorizingUsername); - - 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 override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var request = message as AccessTokenAuthorizationCodeRequest; - if (request != null) { - IAuthorizationCarryingRequest tokenRequest = request; - ((AuthorizationCode)tokenRequest.AuthorizationDescription).VerifyCallback(request.Callback); - - return MessageProtections.None; - } - - return null; - } - } -} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs index cee38db..c073008 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AuthorizationDataBag.cs @@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// A data bag that stores authorization data. /// </summary> - internal abstract class AuthorizationDataBag : DataBag, IAuthorizationDescription { + public abstract class AuthorizationDataBag : DataBag, IAuthorizationDescription { /// <summary> /// Initializes a new instance of the <see cref="AuthorizationDataBag"/> class. /// </summary> @@ -24,7 +24,6 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// Gets or sets the identifier of the client authorized to access protected data. /// </summary> - /// <value></value> [MessagePart] public string ClientIdentifier { get; set; } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs new file mode 100644 index 0000000..b0f86a9 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/ClientAuthenticationResult.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// <copyright file="ClientAuthenticationResult.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2.ChannelElements { + /// <summary> + /// Describes the various levels at which client information may be extracted from an inbound message. + /// </summary> + public enum ClientAuthenticationResult { + /// <summary> + /// No client identification or authentication was discovered. + /// </summary> + NoAuthenticationRecognized, + + /// <summary> + /// The client identified itself, but did not attempt to authenticate itself. + /// </summary> + ClientIdNotAuthenticated, + + /// <summary> + /// The client authenticated itself (provided compelling evidence that it was who it claims to be). + /// </summary> + ClientAuthenticated, + + /// <summary> + /// The client failed in an attempt to authenticate itself, claimed to be an unrecognized client, or otherwise messed up. + /// </summary> + ClientAuthenticationRejected, + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs new file mode 100644 index 0000000..b1e2372 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/IScopeSatisfiedCheck.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="IScopeSatisfiedCheck.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System.Collections.Generic; + + /// <summary> + /// An extensibility point that allows authorization servers and resource servers to customize how scopes may be considered + /// supersets of each other. + /// </summary> + /// <remarks> + /// Implementations must be thread-safe. + /// </remarks> + public interface IScopeSatisfiedCheck { + /// <summary> + /// Checks whether the granted scope is a superset of the required scope. + /// </summary> + /// <param name="requiredScope">The set of strings that the resource server demands in an access token's scope in order to complete some operation.</param> + /// <param name="grantedScope">The set of strings that define the scope within an access token that the client is authorized to.</param> + /// <returns><c>true</c> if <paramref name="grantedScope"/> is a superset of <paramref name="requiredScope"/> to allow the request to proceed; <c>false</c> otherwise.</returns> + /// <remarks> + /// The default reasonable implementation of this is: + /// <code> + /// return <paramref name="grantedScope"/>.IsSupersetOf(<paramref name="requiredScope"/>); + /// </code> + /// <para>In some advanced cases it may not be so simple. One case is that there may be a string that aggregates the capabilities of several others + /// in order to simplify common scenarios. For example, the scope "ReadAll" may represent the same authorization as "ReadProfile", "ReadEmail", and + /// "ReadFriends". + /// </para> + /// <para>Great care should be taken in implementing this method as this is a critical security module for the authorization and resource servers.</para> + /// </remarks> + bool IsScopeSatisfied(HashSet<string> requiredScope, HashSet<string> grantedScope); + } +} diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/OAuth 2 Messages.cd b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/OAuth 2 Messages.cd deleted file mode 100644 index 05e3ad9..0000000 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/OAuth 2 Messages.cd +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<ClassDiagram MajorVersion="1" MinorVersion="1"> - <Class Name="DotNetOpenAuth.OAuth2.Messages.MessageBase" Collapsed="true"> - <Position X="0.5" Y="0.5" Width="1.5" /> - <TypeIdentifier> - <HashCode>IAAMACQAQAAAgAkAAAAIAAYACgAAIAAAIACAACAAAIA=</HashCode> - <FileName>OAuth2\Messages\MessageBase.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenAuthorizationCodeRequest" Collapsed="true"> - <Position X="8.5" Y="6.75" Width="3" /> - <TypeIdentifier> - <HashCode>ACAAEAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAgAAAATAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenAuthorizationCodeRequest.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenClientCredentialsRequest" Collapsed="true"> - <Position X="8.5" Y="8.75" Width="2.75" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenClientCredentialsRequest.cs</FileName> - </TypeIdentifier> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenFailedResponse" Collapsed="true"> - <Position X="3.25" Y="8.5" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAAIAAAAAAAQAAAABAAAQAAAAAAAEQAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenFailedResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenRefreshRequest" Collapsed="true"> - <Position X="8.5" Y="9.75" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAEAAAQAAAAAAAAAAAAAAQAAAAAAAAAAAgAAAABAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenRefreshRequest.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenRequestBase" Collapsed="true"> - <Position X="5.75" Y="5.75" Width="2" /> - <TypeIdentifier> - <HashCode>AAAAAAAAQABAAAAAAAAAAAAQAAAAAAAAAAAAAAAACAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenRequestBase.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenResourceOwnerPasswordCredentialsRequest" Collapsed="true"> - <Position X="8.5" Y="10.5" Width="4" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAACAQAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenResourceOwnerPasswordCredentialsRequest.cs</FileName> - </TypeIdentifier> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessTokenSuccessResponse" Collapsed="true"> - <Position X="3.25" Y="7.5" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAAAAAQAAAACAAAAAAAAQAEAAAAAAQAEAAAAAAAgA=</HashCode> - <FileName>OAuth2\Messages\AccessTokenSuccessResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AuthenticatedClientRequestBase" Collapsed="true"> - <Position X="3.25" Y="5.25" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\AuthenticatedClientRequestBase.cs</FileName> - </TypeIdentifier> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationFailedResponse" Collapsed="true"> - <Position X="3.25" Y="4.5" Width="2.75" /> - <TypeIdentifier> - <HashCode>AAAAAIAAAAAAAQAAAAAAAAgAAAAAAAEAAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationFailedResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationRequest" Collapsed="true"> - <Position X="3.25" Y="0.5" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAAAAAQABAACAAAAAAAACAAAQAAAQAAAAAAAAAQAA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationRequest.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationSuccessAccessTokenResponse" Collapsed="true"> - <Position X="6.25" Y="3.75" Width="3.75" /> - <TypeIdentifier> - <HashCode>AAAAEAAAAAAAAAAAAAAAAAACEAAAAAAAAAAgAAAABgA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationSuccessAccessTokenResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationSuccessAuthCodeResponse" Collapsed="true"> - <Position X="6.25" Y="2.5" Width="3.5" /> - <TypeIdentifier> - <HashCode>ACAAEAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAgAAAABAA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationSuccessResponseBase" Collapsed="true"> - <Position X="3.25" Y="1.5" Width="2.75" /> - <TypeIdentifier> - <HashCode>AAACAAAAAAAAACAAAAAAAAgAAAAAAAAAAEAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationSuccessResponseBase.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.AccessProtectedResourceRequest" Collapsed="true"> - <Position X="3.25" Y="9.75" Width="2.5" /> - <TypeIdentifier> - <HashCode>AAAAEAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAgAAAABgA=</HashCode> - <FileName>OAuth2\Messages\AccessProtectedResourceRequest.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.UnauthorizedResponse" Collapsed="true"> - <Position X="3.25" Y="10.75" Width="2" /> - <TypeIdentifier> - <HashCode>AUABAAAAAAAAACAAAAAAAAQIAAAAAAAQAAAAAAAAABA=</HashCode> - <FileName>OAuth2\Messages\UnauthorizedResponse.cs</FileName> - </TypeIdentifier> - <Lollipop Position="0.2" /> - </Class> - <Class Name="DotNetOpenAuth.OAuth2.Messages.ScopedAccessTokenRequest" Collapsed="true"> - <Position X="6.75" Y="7.75" Width="2.25" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAACAA=</HashCode> - <FileName>OAuth2\Messages\ScopedAccessTokenRequest.cs</FileName> - </TypeIdentifier> - </Class> - <Interface Name="DotNetOpenAuth.OAuth2.Messages.IMessageWithClientState"> - <Position X="11.5" Y="0.5" Width="2" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\IMessageWithClientState.cs</FileName> - </TypeIdentifier> - </Interface> - <Interface Name="DotNetOpenAuth.OAuth2.ChannelElements.IAuthorizationCarryingRequest"> - <Position X="11.75" Y="2" Width="2.5" /> - <TypeIdentifier> - <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAEAAAAA=</HashCode> - <FileName>OAuth2\ChannelElements\IAuthorizationCarryingRequest.cs</FileName> - </TypeIdentifier> - </Interface> - <Enum Name="DotNetOpenAuth.OAuth2.Messages.EndUserAuthorizationResponseType"> - <Position X="8" Y="0.5" Width="3" /> - <TypeIdentifier> - <HashCode>ACAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\EndUserAuthorizationResponseType.cs</FileName> - </TypeIdentifier> - </Enum> - <Enum Name="DotNetOpenAuth.OAuth2.Messages.GrantType"> - <Position X="6.25" Y="0.5" Width="1.5" /> - <TypeIdentifier> - <HashCode>ACAAAAAAQAAAAAQAAgAAAAAAAAAAAAACAAAAAAAAAAA=</HashCode> - <FileName>OAuth2\Messages\GrantType.cs</FileName> - </TypeIdentifier> - </Enum> - <Font Name="Segoe UI" Size="9" /> -</ClassDiagram>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs index 3f4bb5b..e4a8a48 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/UnauthorizedResponse.cs @@ -6,110 +6,204 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System; + using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Globalization; using System.Net; using System.Text; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; /// <summary> - /// A direct response that is simply a 401 Unauthorized with an - /// WWW-Authenticate: OAuth header. + /// A direct response sent in response to a rejected Bearer access token. /// </summary> - internal class UnauthorizedResponse : MessageBase, IHttpDirectResponse { + /// <remarks> + /// This satisfies the spec in: http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header + /// </remarks> + public class UnauthorizedResponse : MessageBase, IHttpDirectResponse { /// <summary> - /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. + /// The headers in the response message. /// </summary> - /// <param name="exception">The exception.</param> - /// <param name="version">The protocol version.</param> - internal UnauthorizedResponse(ProtocolException exception, Version version = null) - : base(version ?? Protocol.Default.Version) { - Requires.NotNull(exception, "exception"); - this.ErrorMessage = exception.Message; - } + private readonly WebHeaderCollection headers = new WebHeaderCollection(); /// <summary> /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. /// </summary> - /// <param name="request">The request.</param> - internal UnauthorizedResponse(IDirectedProtocolMessage request) - : base(request) { - this.Realm = "Service"; + /// <param name="version">The protocol version.</param> + protected UnauthorizedResponse(Version version = null) + : base(version ?? Protocol.Default.Version) { } /// <summary> /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. /// </summary> /// <param name="request">The request.</param> - /// <param name="exception">The exception.</param> - internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception) - : this(request) { - Requires.NotNull(exception, "exception"); - this.ErrorMessage = exception.Message; + protected UnauthorizedResponse(IDirectedProtocolMessage request) + : base(request) { } #region IHttpDirectResponse Members /// <summary> - /// Gets the HTTP status code that the direct response should be sent with. + /// Gets or sets the HTTP status code that the direct response should be sent with. /// </summary> - HttpStatusCode IHttpDirectResponse.HttpStatusCode { - get { return HttpStatusCode.Unauthorized; } - } + public HttpStatusCode HttpStatusCode { get; set; } /// <summary> /// Gets the HTTP headers to add to the response. /// </summary> /// <value>May be an empty collection, but must not be <c>null</c>.</value> - WebHeaderCollection IHttpDirectResponse.Headers { - get { - return new WebHeaderCollection() { - { HttpResponseHeader.WwwAuthenticate, Protocol.BearerHttpAuthorizationScheme }, - }; - } + public WebHeaderCollection Headers { + get { return this.headers; } } #endregion /// <summary> - /// Gets or sets the error message. + /// Gets or sets the well known error code. /// </summary> - /// <value>The error message.</value> - [MessagePart("error")] - internal string ErrorMessage { get; set; } + /// <value>One of the values from <see cref="Protocol.BearerTokenErrorCodes"/>.</value> + [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode)] + public string ErrorCode { get; set; } + + /// <summary> + /// Gets or sets a human-readable explanation for developers that is not meant to be displayed to end users. + /// </summary> + [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription)] + public string ErrorDescription { get; set; } + + /// <summary> + /// Gets or sets an absolute URI identifying a human-readable web page explaining the error. + /// </summary> + [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri)] + public Uri ErrorUri { get; set; } /// <summary> /// Gets or sets the realm. /// </summary> /// <value>The realm.</value> - [MessagePart("realm")] - internal string Realm { get; set; } + [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Realm)] + public string Realm { get; set; } /// <summary> /// Gets or sets the scope. /// </summary> /// <value>The scope.</value> - [MessagePart("scope")] - internal string Scope { get; set; } + [MessagePart(Protocol.BearerTokenUnauthorizedResponseParameters.Scope, Encoder = typeof(ScopeEncoder))] + public HashSet<string> Scope { get; set; } + + /// <summary> + /// Gets the scheme to use in the WWW-Authenticate header. + /// </summary> + internal virtual string Scheme { + get { return Protocol.BearerHttpAuthorizationScheme; } + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class + /// to inform the client that the request was invalid. + /// </summary> + /// <param name="exception">The exception.</param> + /// <param name="version">The version of OAuth 2 that is in use.</param> + /// <returns>The error message.</returns> + internal static UnauthorizedResponse InvalidRequest(ProtocolException exception, Version version = null) { + Requires.NotNull(exception, "exception"); + var message = new UnauthorizedResponse(version) { + ErrorCode = Protocol.BearerTokenErrorCodes.InvalidRequest, + ErrorDescription = exception.Message, + HttpStatusCode = System.Net.HttpStatusCode.BadRequest, + }; + + return message; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class + /// to inform the client that the bearer token included in the request was rejected. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="exception">The exception.</param> + /// <returns>The error message.</returns> + internal static UnauthorizedResponse InvalidToken(IDirectedProtocolMessage request, ProtocolException exception) { + Requires.NotNull(request, "request"); + Requires.NotNull(exception, "exception"); + var message = new UnauthorizedResponse(request) { + ErrorCode = Protocol.BearerTokenErrorCodes.InvalidToken, + ErrorDescription = exception.Message, + HttpStatusCode = System.Net.HttpStatusCode.Unauthorized, + }; + + return message; + } /// <summary> - /// Gets or sets the algorithms. + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class + /// to inform the client of the required set of scopes required to perform this operation. /// </summary> - /// <value>The algorithms.</value> - [MessagePart("algorithms")] - internal string Algorithms { get; set; } + /// <param name="request">The request.</param> + /// <param name="requiredScopes">The set of scopes required to perform this operation.</param> + /// <returns>The error message.</returns> + internal static UnauthorizedResponse InsufficientScope(IDirectedProtocolMessage request, HashSet<string> requiredScopes) { + Requires.NotNull(request, "request"); + Requires.NotNull(requiredScopes, "requiredScopes"); + var message = new UnauthorizedResponse(request) { + HttpStatusCode = System.Net.HttpStatusCode.Forbidden, + ErrorCode = Protocol.BearerTokenErrorCodes.InsufficientScope, + Scope = requiredScopes, + }; + return message; + } /// <summary> - /// Gets or sets the user endpoint. + /// Ensures the message is valid. /// </summary> - /// <value>The user endpoint.</value> - [MessagePart("user-uri")] - internal Uri UserEndpoint { get; set; } + protected override void EnsureValidMessage() { + base.EnsureValidMessage(); + + // Make sure the characters used in the supplied parameters satisfy requirements. + VerifyErrorCodeOrDescription(this.ErrorCode, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorCode); + VerifyErrorCodeOrDescription(this.ErrorDescription, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorDescription); + VerifyErrorUri(this.ErrorUri); + + // Ensure that at least one parameter is specified, as required in the spec. + ErrorUtilities.VerifyProtocol( + this.ErrorCode != null || this.ErrorDescription != null || this.ErrorUri != null || this.Realm != null || this.Scope != null, + OAuthStrings.BearerTokenUnauthorizedAtLeastOneParameterRequired); + } /// <summary> - /// Gets or sets the token endpoint. + /// Ensures the error or error_description parameters contain only allowed characters. /// </summary> - /// <value>The token endpoint.</value> - [MessagePart("token-uri")] - internal Uri TokenEndpoint { get; set; } + /// <param name="value">The argument.</param> + /// <param name="parameterName">The name of the parameter being validated. Used when errors are reported.</param> + private static void VerifyErrorCodeOrDescription(string value, string parameterName) { + if (value != null) { + for (int i = 0; i < value.Length; i++) { + // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header + char ch = value[i]; + if (!((ch >= '\x20' && ch <= '\x21') || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) { + ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, parameterName, ch); + } + } + } + } + + /// <summary> + /// Ensures the error_uri parameter contains only allowed characters and is an absolute URI. + /// </summary> + /// <param name="valueUri">The absolute URI.</param> + private static void VerifyErrorUri(Uri valueUri) { + if (valueUri != null) { + ErrorUtilities.VerifyProtocol(valueUri.IsAbsoluteUri, OAuthStrings.AbsoluteUriRequired); + string value = valueUri.AbsoluteUri; + for (int i = 0; i < value.Length; i++) { + // The allowed set of characters comes from http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header + char ch = value[i]; + if (!(ch == '\x21' || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) { + ErrorUtilities.ThrowProtocol(OAuthStrings.ParameterContainsIllegalCharacters, Protocol.BearerTokenUnauthorizedResponseParameters.ErrorUri, ch); + } + } + } + } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs index 6ce3b53..b440c1f 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.239 +// Runtime Version:4.0.30319.17622 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -70,15 +70,6 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to The requested access scope ("{0}") exceeds the grant scope ("{1}").. - /// </summary> - internal static string AccessScopeExceedsGrantScope { - get { - return ResourceManager.GetString("AccessScopeExceedsGrantScope", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to The access token contains characters that must not appear in the HTTP Authorization header.. /// </summary> internal static string AccessTokenInvalidForHttpAuthorizationHeader { @@ -88,29 +79,11 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to Failed to obtain access token. Authorization Server reports reason: {0}. - /// </summary> - internal static string CannotObtainAccessTokenWithReason { - get { - return ResourceManager.GetString("CannotObtainAccessTokenWithReason", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The callback URL ({0}) is not allowed for this client.. - /// </summary> - internal static string ClientCallbackDisallowed { - get { - return ResourceManager.GetString("ClientCallbackDisallowed", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Failure looking up secret for client or token.. + /// Looks up a localized string similar to At least one parameter is required for the Bearer scheme in its WWW-Authenticate header.. /// </summary> - internal static string ClientOrTokenSecretNotFound { + internal static string BearerTokenUnauthorizedAtLeastOneParameterRequired { get { - return ResourceManager.GetString("ClientOrTokenSecretNotFound", resourceCulture); + return ResourceManager.GetString("BearerTokenUnauthorizedAtLeastOneParameterRequired", resourceCulture); } } @@ -124,15 +97,6 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to Failed to obtain access token due to invalid Client Identifier or Client Secret.. - /// </summary> - internal static string InvalidClientCredentials { - get { - return ResourceManager.GetString("InvalidClientCredentials", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to The scope token "{0}" contains illegal characters or is empty.. /// </summary> internal static string InvalidScopeToken { @@ -142,15 +106,6 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to No callback URI was available for this request.. - /// </summary> - internal static string NoCallback { - get { - return ResourceManager.GetString("NoCallback", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to Refresh tokens should not be granted without the request including an access grant.. /// </summary> internal static string NoGrantNoRefreshToken { @@ -160,11 +115,11 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Looks up a localized string similar to The request message type {0} should not be responded to with a refresh token.. + /// Looks up a localized string similar to The '{0}' parameter contains the illegal character '{1}'.. /// </summary> - internal static string RefreshTokenInappropriateForRequestType { + internal static string ParameterContainsIllegalCharacters { get { - return ResourceManager.GetString("RefreshTokenInappropriateForRequestType", resourceCulture); + return ResourceManager.GetString("ParameterContainsIllegalCharacters", resourceCulture); } } @@ -185,14 +140,5 @@ namespace DotNetOpenAuth.OAuth2 { return ResourceManager.GetString("ScopesMayNotContainSpaces", resourceCulture); } } - - /// <summary> - /// Looks up a localized string similar to Unexpected response Content-Type {0}. - /// </summary> - internal static string UnexpectedResponseContentType { - get { - return ResourceManager.GetString("UnexpectedResponseContentType", resourceCulture); - } - } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx index af1a955..4298af6 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthStrings.resx @@ -112,46 +112,31 @@ <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="AbsoluteUriRequired" xml:space="preserve"> <value>The value for message part "{0}" must be an absolute URI.</value> </data> - <data name="AccessScopeExceedsGrantScope" xml:space="preserve"> - <value>The requested access scope ("{0}") exceeds the grant scope ("{1}").</value> - </data> <data name="AccessTokenInvalidForHttpAuthorizationHeader" xml:space="preserve"> <value>The access token contains characters that must not appear in the HTTP Authorization header.</value> </data> - <data name="CannotObtainAccessTokenWithReason" xml:space="preserve"> - <value>Failed to obtain access token. Authorization Server reports reason: {0}</value> - </data> - <data name="ClientCallbackDisallowed" xml:space="preserve"> - <value>The callback URL ({0}) is not allowed for this client.</value> - </data> - <data name="ClientOrTokenSecretNotFound" xml:space="preserve"> - <value>Failure looking up secret for client or token.</value> + <data name="BearerTokenUnauthorizedAtLeastOneParameterRequired" xml:space="preserve"> + <value>At least one parameter is required for the Bearer scheme in its WWW-Authenticate header.</value> </data> <data name="HttpsRequired" xml:space="preserve"> <value>This message can only be sent over HTTPS.</value> </data> - <data name="InvalidClientCredentials" xml:space="preserve"> - <value>Failed to obtain access token due to invalid Client Identifier or Client Secret.</value> - </data> <data name="InvalidScopeToken" xml:space="preserve"> <value>The scope token "{0}" contains illegal characters or is empty.</value> </data> - <data name="NoCallback" xml:space="preserve"> - <value>No callback URI was available for this request.</value> - </data> <data name="NoGrantNoRefreshToken" xml:space="preserve"> <value>Refresh tokens should not be granted without the request including an access grant.</value> </data> - <data name="RefreshTokenInappropriateForRequestType" xml:space="preserve"> - <value>The request message type {0} should not be responded to with a refresh token.</value> + <data name="ParameterContainsIllegalCharacters" xml:space="preserve"> + <value>The '{0}' parameter contains the illegal character '{1}'.</value> </data> <data name="ResultShouldNotBeNull" xml:space="preserve"> <value>The return value of {0}.{1} should never be null.</value> @@ -159,7 +144,4 @@ <data name="ScopesMayNotContainSpaces" xml:space="preserve"> <value>Individual scopes may not contain spaces.</value> </data> - <data name="UnexpectedResponseContentType" xml:space="preserve"> - <value>Unexpected response Content-Type {0}</value> - </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs index dd7909b..5a4a0d3 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs @@ -24,9 +24,24 @@ namespace DotNetOpenAuth.OAuth2 { public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal; /// <summary> + /// The string "Basic ". + /// </summary> + private const string HttpBasicAuthScheme = "Basic "; + + /// <summary> /// The delimiter between scope elements. /// </summary> - private static char[] scopeDelimiter = new char[] { ' ' }; + private static readonly char[] scopeDelimiter = new char[] { ' ' }; + + /// <summary> + /// A colon, in a 1-length character array. + /// </summary> + private static readonly char[] ColonSeparator = new char[] { ':' }; + + /// <summary> + /// The encoding to use when preparing credentials for transit in HTTP Basic base64 encoding form. + /// </summary> + private static readonly Encoding HttpBasicEncoding = Encoding.UTF8; /// <summary> /// The characters that may appear in an access token that is included in an HTTP Authorization header. @@ -35,32 +50,9 @@ namespace DotNetOpenAuth.OAuth2 { /// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header) /// </remarks> private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters + - MessagingUtilities.LowercaseLetters + - MessagingUtilities.Digits + - @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;"; - - /// <summary> - /// Determines whether one given scope is a subset of another scope. - /// </summary> - /// <param name="requestedScope">The requested scope, which may be a subset of <paramref name="grantedScope"/>.</param> - /// <param name="grantedScope">The granted scope, the suspected superset.</param> - /// <returns> - /// <c>true</c> if all the elements that appear in <paramref name="requestedScope"/> also appear in <paramref name="grantedScope"/>; - /// <c>false</c> otherwise. - /// </returns> - public static bool IsScopeSubset(string requestedScope, string grantedScope) { - if (string.IsNullOrEmpty(requestedScope)) { - return true; - } - - if (string.IsNullOrEmpty(grantedScope)) { - return false; - } - - var requestedScopes = new HashSet<string>(requestedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); - var grantedScopes = new HashSet<string>(grantedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); - return requestedScopes.IsSubsetOf(grantedScopes); - } + MessagingUtilities.LowercaseLetters + + MessagingUtilities.Digits + + @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;"; /// <summary> /// Identifies individual scope elements @@ -89,6 +81,26 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> + /// Parses a space-delimited list of scopes into a set. + /// </summary> + /// <param name="scopes">The space-delimited string.</param> + /// <returns>A set.</returns> + internal static HashSet<string> ParseScopeSet(string scopes) { + Requires.NotNull(scopes, "scopes"); + return ParseScopeSet(scopes.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); + } + + /// <summary> + /// Creates a set out of an array of strings. + /// </summary> + /// <param name="scopes">The array of strings.</param> + /// <returns>A set.</returns> + internal static HashSet<string> ParseScopeSet(string[] scopes) { + Requires.NotNull(scopes, "scopes"); + return new HashSet<string>(scopes, StringComparer.Ordinal); + } + + /// <summary> /// Verifies that a sequence of scope tokens are all valid. /// </summary> /// <param name="scopes">The scopes.</param> @@ -131,24 +143,43 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets information about the client with a given identifier. + /// Applies the HTTP Authorization header for HTTP Basic authentication. + /// </summary> + /// <param name="headers">The headers collection to set the authorization header to.</param> + /// <param name="userName">The username. Cannot be empty.</param> + /// <param name="password">The password. Cannot be null.</param> + internal static void ApplyHttpBasicAuth(WebHeaderCollection headers, string userName, string password) { + Requires.NotNull(headers, "headers"); + Requires.NotNullOrEmpty(userName, "userName"); + Requires.NotNull(password, "password"); + + string concat = userName + ":" + password; + byte[] bits = HttpBasicEncoding.GetBytes(concat); + string base64 = Convert.ToBase64String(bits); + string header = HttpBasicAuthScheme + base64; + headers[HttpRequestHeader.Authorization] = header; + } + + /// <summary> + /// Extracts the username and password from an HTTP Basic authorized web header. /// </summary> - /// <param name="authorizationServer">The authorization server.</param> - /// <param name="clientIdentifier">The client identifier.</param> - /// <returns>The client information. Never null.</returns> - internal static IClientDescription GetClientOrThrow(this IAuthorizationServer authorizationServer, string clientIdentifier) { - Requires.NotNullOrEmpty(clientIdentifier, "clientIdentifier"); - Contract.Ensures(Contract.Result<IClientDescription>() != null); - - try { - var result = authorizationServer.GetClient(clientIdentifier); - ErrorUtilities.VerifyHost(result != null, OAuthStrings.ResultShouldNotBeNull, authorizationServer.GetType().FullName, "GetClient(string)"); - return result; - } catch (KeyNotFoundException ex) { - throw ErrorUtilities.Wrap(ex, OAuthStrings.ClientOrTokenSecretNotFound); - } catch (ArgumentException ex) { - throw ErrorUtilities.Wrap(ex, OAuthStrings.ClientOrTokenSecretNotFound); + /// <param name="headers">The incoming web headers.</param> + /// <returns>The network credentials; or <c>null</c> if none could be discovered in the request.</returns> + internal static NetworkCredential ParseHttpBasicAuth(WebHeaderCollection headers) { + Requires.NotNull(headers, "headers"); + + string authorizationHeader = headers[HttpRequestHeaders.Authorization]; + if (authorizationHeader != null && authorizationHeader.StartsWith(HttpBasicAuthScheme, StringComparison.Ordinal)) { + string base64 = authorizationHeader.Substring(HttpBasicAuthScheme.Length); + byte[] bits = Convert.FromBase64String(base64); + string usernameColonPassword = HttpBasicEncoding.GetString(bits); + string[] usernameAndPassword = usernameColonPassword.Split(ColonSeparator, 2); + if (usernameAndPassword.Length == 2) { + return new NetworkCredential(usernameAndPassword[0], usernameAndPassword[1]); + } } + + return null; } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs index 19fe845..d780a81 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Protocol.cs @@ -45,11 +45,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string BearerTokenEncodedUrlParameterName = "access_token"; /// <summary> - /// The "type" string. - /// </summary> - internal const string type = "type"; - - /// <summary> /// The "state" string. /// </summary> internal const string state = "state"; @@ -60,26 +55,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string redirect_uri_mismatch = "redirect_uri_mismatch"; /// <summary> - /// The "bad_verification_code" string. - /// </summary> - internal const string bad_verification_code = "bad_verification_code"; - - /// <summary> - /// The "incorrect_client_credentials" string. - /// </summary> - internal const string incorrect_client_credentials = "incorrect_client_credentials"; - - /// <summary> - /// The "unauthorized_client" string. - /// </summary> - internal const string unauthorized_client = "unauthorized_client"; - - /// <summary> - /// The "authorization_expired" string. - /// </summary> - internal const string authorization_expired = "authorization_expired"; - - /// <summary> /// The "redirect_uri" string. /// </summary> internal const string redirect_uri = "redirect_uri"; @@ -95,11 +70,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string scope = "scope"; /// <summary> - /// The "immediate" string. - /// </summary> - internal const string immediate = "immediate"; - - /// <summary> /// The "client_secret" string. /// </summary> internal const string client_secret = "client_secret"; @@ -110,21 +80,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string code = "code"; /// <summary> - /// The "user_code" string. - /// </summary> - internal const string user_code = "user_code"; - - /// <summary> - /// The "verification_uri" string. - /// </summary> - internal const string verification_uri = "verification_uri"; - - /// <summary> - /// The "interval" string. - /// </summary> - internal const string interval = "interval"; - - /// <summary> /// The "error" string. /// </summary> internal const string error = "error"; @@ -135,11 +90,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string access_token = "access_token"; /// <summary> - /// The "access_token_secret" string. - /// </summary> - internal const string access_token_secret = "access_token_secret"; - - /// <summary> /// The "token_type" string. /// </summary> internal const string token_type = "token_type"; @@ -155,11 +105,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string expires_in = "expires_in"; /// <summary> - /// The "expired_delegation_code" string. - /// </summary> - internal const string expired_delegation_code = "expired_delegation_code"; - - /// <summary> /// The "username" string. /// </summary> internal const string username = "username"; @@ -170,26 +115,6 @@ namespace DotNetOpenAuth.OAuth2 { internal const string password = "password"; /// <summary> - /// The "format" string. - /// </summary> - internal const string format = "format"; - - /// <summary> - /// The "assertion" string. - /// </summary> - internal const string assertion = "assertion"; - - /// <summary> - /// The "assertion_type" string. - /// </summary> - internal const string assertion_type = "assertion_type"; - - /// <summary> - /// The "user_denied" string. - /// </summary> - internal const string user_denied = "user_denied"; - - /// <summary> /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol. /// </summary> internal static readonly Protocol V20 = new Protocol { @@ -286,27 +211,38 @@ namespace DotNetOpenAuth.OAuth2 { internal static class AccessTokenRequestErrorCodes { /// <summary> - /// The request is missing a required parameter, includes an unknown parameter or parameter value, repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed. + /// The request is missing a required parameter, includes an unknown parameter or parameter value, repeats a parameter, + /// includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed. /// </summary> internal const string InvalidRequest = "invalid_request"; /// <summary> - /// The client is not authorized to use the access grant type provided. + /// Client authentication failed (e.g. unknown client, no client authentication included, or unsupported authentication method). + /// The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. + /// If the client attempted to authenticate via the Authorization request header field, the authorization server MUST respond with + /// an HTTP 401 (Unauthorized) status code, and include the WWW-Authenticate response header field matching the authentication scheme + /// used by the client. /// </summary> - internal const string UnauthorizedClient = "unauthorized_client"; + internal const string InvalidClient = "invalid_client"; /// <summary> - /// The resource owner or authorization server denied the request. + /// The provided authorization grant (e.g. authorization code, resource owner credentials) or refresh token is invalid, expired, + /// revoked, does not match the redirection URI used in the authorization request, or was issued to another client. /// </summary> - internal const string AccessDenied = "access_denied"; + internal const string InvalidGrant = "invalid_grant"; /// <summary> - /// The authorization server does not support obtaining an access token using this method. + /// The authenticated client is not authorized to use this authorization grant type. /// </summary> - internal const string UnsupportedGrantType = "unsupported_response_type"; + internal const string UnauthorizedClient = "unauthorized_client"; /// <summary> - /// The requested scope is invalid, unknown, malformed, or exceeds the previously granted scope. + /// The authorization grant type is not supported by the authorization server. + /// </summary> + internal const string UnsupportedGrantType = "unsupported_grant_type"; + + /// <summary> + /// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner. /// </summary> internal const string InvalidScope = "invalid_scope"; } @@ -361,5 +297,39 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> internal const string Bearer = "bearer"; } + + internal static class BearerTokenUnauthorizedResponseParameters { + internal const string Realm = "realm"; + internal const string ErrorCode = "error"; + internal const string ErrorDescription = "error_description"; + internal const string ErrorUri = "error_uri"; + internal const string Scope = "scope"; + } + + /// <summary> + /// The error codes prescribed in http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#resource-error-codes + /// </summary> + internal static class BearerTokenErrorCodes { + /// <summary> + /// The request is missing a required parameter, includes an unsupported parameter or parameter value, + /// repeats the same parameter, uses more than one method for including an access token, or is otherwise + /// malformed. The resource server SHOULD respond with the HTTP 400 (Bad Request) status code. + /// </summary> + internal const string InvalidRequest = "invalid_request"; + + /// <summary> + /// The access token provided is expired, revoked, malformed, or invalid for other reasons. + /// The resource SHOULD respond with the HTTP 401 (Unauthorized) status code. The client MAY request + /// a new access token and retry the protected resource request. + /// </summary> + internal const string InvalidToken = "invalid_token"; + + /// <summary> + /// The request requires higher privileges than provided by the access token. The resource server + /// SHOULD respond with the HTTP 403 (Forbidden) status code and MAY include the scope attribute + /// with the scope necessary to access the protected resource. + /// </summary> + internal const string InsufficientScope = "insufficient_scope"; + } } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs new file mode 100644 index 0000000..684e4a8 --- /dev/null +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/StandardScopeSatisfiedCheck.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="StandardScopeSatisfiedCheck.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth2 { + using System.Collections.Generic; + + /// <summary> + /// The default scope superset checker, which assumes that no scopes overlap. + /// </summary> + internal class StandardScopeSatisfiedCheck : IScopeSatisfiedCheck { + /// <summary> + /// Checks whether the granted scope is a superset of the required scope. + /// </summary> + /// <param name="requiredScope">The set of strings that the resource server demands in an access token's scope in order to complete some operation.</param> + /// <param name="grantedScope">The set of strings that define the scope within an access token that the client is authorized to.</param> + /// <returns><c>true</c> if <paramref name="grantedScope"/> is a superset of <paramref name="requiredScope"/> to allow the request to proceed; <c>false</c> otherwise.</returns> + /// <remarks> + /// The default reasonable implementation of this is: + /// <code> + /// return <paramref name="grantedScope"/>.IsSupersetOf(<paramref name="requiredScope"/>); + /// </code> + /// <para>In some advanced cases it may not be so simple. One case is that there may be a string that aggregates the capabilities of several others + /// in order to simplify common scenarios. For example, the scope "ReadAll" may represent the same authorization as "ReadProfile", "ReadEmail", and + /// "ReadFriends". + /// </para> + /// <para>Great care should be taken in implementing this method as this is a critical security module for the authorization and resource servers.</para> + /// </remarks> + public bool IsScopeSatisfied(HashSet<string> requiredScope, HashSet<string> grantedScope) { + Requires.NotNull(requiredScope, "requiredScope"); + Requires.NotNull(grantedScope, "grantedScope"); + return grantedScope.IsSupersetOf(requiredScope); + } + } +} diff --git a/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs index 14fb526..26b1318 100644 --- a/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs +++ b/src/DotNetOpenAuth.OAuth2/Properties/AssemblyInfo.cs @@ -50,11 +50,13 @@ using System.Web.UI; [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ClientAuthorization, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] #else [assembly: InternalsVisibleTo("DotNetOpenAuth.Test")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.AuthorizationServer")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ResourceServer")] [assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.Client")] +[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth2.ClientAuthorization")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] #endif diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs index 6ffb326..946d354 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/Extensions/ExtensionsInteropHelper.cs @@ -52,7 +52,7 @@ namespace DotNetOpenAuth.OpenId.Provider.Extensions { var ax = req.GetExtension<FetchRequest>(); if (ax != null) { - sreg = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + sreg = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.TypeUris.Standard); sreg.Synthesized = true; ((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg); sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate); diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs index 594803d..5c39c5e 100644 --- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs +++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs @@ -68,9 +68,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> public Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket); - AssociationDataBag bag; + AssociationDataBag bag = new AssociationDataBag(); try { - bag = formatter.Deserialize(containingMessage, handle, Protocol.Default.openid.assoc_handle); + formatter.Deserialize(bag, containingMessage, handle, Protocol.Default.openid.assoc_handle); } catch (ProtocolException ex) { Logger.OpenId.Error("Rejecting an association because deserialization of the encoded handle failed.", ex); return null; diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs index 749659e..7d8c050 100644 --- a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs +++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs @@ -49,7 +49,8 @@ namespace DotNetOpenAuth.Configuration { /// <summary> /// The built-in set of identifier discovery services. /// </summary> - private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) }); + private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices = + new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) }); /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class. diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs index 5cd4904..1d795da 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs @@ -21,7 +21,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// match to namespaces for backward compatibility with other OpenID libraries. /// </summary> private static readonly Dictionary<string, string> typeUriToAliasAffinity = new Dictionary<string, string> { - { Extensions.SimpleRegistration.Constants.sreg_ns, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias }, + { Extensions.SimpleRegistration.Constants.TypeUris.Standard, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias }, { Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.CompatibilityAlias }, }; diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs index 373134d..880a25e 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -71,10 +71,10 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { /// actively authenticated to the OP in a manner fitting the asserted policies. /// </summary> /// <remarks> - /// If the RP's request included the "openid.max_auth_age" parameter - /// then the OP MUST include "openid.auth_time" in its response. - /// If "openid.max_auth_age" was not requested, the OP MAY choose to include - /// "openid.auth_time" in its response. + /// If the RP's request included the "openid.pape.max_auth_age" parameter + /// then the OP MUST include "openid.pape.auth_time" in its response. + /// If "openid.pape.max_auth_age" was not requested, the OP MAY choose to include + /// "openid.pape.auth_time" in its response. /// </remarks> [MessagePart("auth_time", Encoder = typeof(DateTimeEncoder))] public DateTime? AuthenticationTimeUtc { diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs index 9d418ee..ab08cbb 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// The factory method that may be used in deserialization of this message. /// </summary> internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if (typeUri == Constants.sreg_ns && isProviderRole) { + if (typeUri == Constants.TypeUris.Standard && isProviderRole) { return new ClaimsRequest(typeUri); } @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// Initializes a new instance of the <see cref="ClaimsRequest"/> class. /// </summary> public ClaimsRequest() - : base(new Version(1, 0), Constants.sreg_ns, Constants.AdditionalTypeUris) { + : base(new Version(1, 0), Constants.TypeUris.Standard, Constants.AdditionalTypeUris) { } /// <summary> diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs index 999fe8d..af60596 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// The factory method that may be used in deserialization of this message. /// </summary> internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if ((typeUri == Constants.sreg_ns || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) { + if ((typeUri == Constants.TypeUris.Standard || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) { return new ClaimsResponse(typeUri); } @@ -55,10 +55,11 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { private CultureInfo culture; /// <summary> - /// Initializes a new instance of the <see cref="ClaimsResponse"/> class. + /// Initializes a new instance of the <see cref="ClaimsResponse"/> class + /// using the most common, and spec prescribed type URI. /// </summary> - internal ClaimsResponse() - : this(Constants.sreg_ns) { + public ClaimsResponse() + : this(Constants.TypeUris.Standard) { } /// <summary> @@ -67,8 +68,10 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// <param name="typeUriToUse"> /// The type URI that must be used to identify this extension in the response message. /// This value should be the same one the relying party used to send the extension request. + /// Commonly used type URIs supported by relying parties are defined in the + /// <see cref="Constants.TypeUris"/> class. /// </param> - internal ClaimsResponse(string typeUriToUse) + public ClaimsResponse(string typeUriToUse = Constants.TypeUris.Standard) : base(new Version(1, 0), typeUriToUse, Constants.AdditionalTypeUris) { Requires.NotNullOrEmpty(typeUriToUse, "typeUriToUse"); } diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs index 8325b0c..30cd748 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs @@ -13,10 +13,31 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// <summary> /// Simple Registration constants /// </summary> - internal static class Constants { - internal const string sreg_ns = "http://openid.net/extensions/sreg/1.1"; - internal const string sreg_ns10 = "http://openid.net/sreg/1.0"; - internal const string sreg_ns11other = "http://openid.net/sreg/1.1"; + public static class Constants { + /// <summary> + /// Commonly used type URIs to represent the Simple Registration extension. + /// </summary> + public static class TypeUris { + /// <summary> + /// The URI "http://openid.net/extensions/sreg/1.1". + /// </summary> + /// <remarks> + /// This is the type URI prescribed by the Simple Registration 1.1 spec. + /// http://openid.net/specs/openid-simple-registration-extension-1_1-01.html#anchor3 + /// </remarks> + public const string Standard = "http://openid.net/extensions/sreg/1.1"; + + /// <summary> + /// The URI "http://openid.net/sreg/1.0" + /// </summary> + public const string Variant10 = "http://openid.net/sreg/1.0"; + + /// <summary> + /// The URI "http://openid.net/sreg/1.1" + /// </summary> + public const string Variant11 = "http://openid.net/sreg/1.1"; + } + internal const string sreg_compatibility_alias = "sreg"; internal const string policy_url = "policy_url"; internal const string optional = "optional"; @@ -39,8 +60,8 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// Additional type URIs that this extension is sometimes known by remote parties. /// </summary> internal static readonly string[] AdditionalTypeUris = new string[] { - Constants.sreg_ns10, - Constants.sreg_ns11other, + Constants.TypeUris.Variant10, + Constants.TypeUris.Variant11, }; } } diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs index 28e4df0..8f1baed 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs @@ -116,11 +116,7 @@ namespace DotNetOpenAuth.OpenId { Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); Contract.Ensures(Contract.Result<Realm>() != null); - 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; + var realmUrl = new UriBuilder(MessagingUtilities.GetWebRoot()); // For RP discovery, the realm url MUST NOT redirect. To prevent this for // virtual directory hosted apps, we need to make sure that the realm path ends diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 00c1bb4..84bdf7d 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -219,6 +219,7 @@ <Compile Include="Mocks\CoordinatingChannel.cs" /> <Compile Include="Mocks\CoordinatingHttpRequestInfo.cs" /> <Compile Include="Mocks\CoordinatingOAuth2AuthServerChannel.cs" /> + <Compile Include="Mocks\CoordinatingOAuth2ClientChannel.cs" /> <Compile Include="Mocks\CoordinatingOutgoingWebResponse.cs" /> <Compile Include="Mocks\CoordinatingOAuthConsumerChannel.cs" /> <Compile Include="Mocks\IBaseMessageExplicitMembers.cs" /> @@ -233,6 +234,7 @@ <Compile Include="Mocks\TestBaseMessage.cs" /> <Compile Include="Mocks\TestDerivedMessage.cs" /> <Compile Include="Mocks\TestDirectResponseMessageWithHttpStatus.cs" /> + <Compile Include="Mocks\TestMessageWithDate.cs" /> <Compile Include="Mocks\TestReplayProtectedMessage.cs" /> <Compile Include="Mocks\TestDirectedMessage.cs" /> <Compile Include="Mocks\TestBadChannel.cs" /> @@ -437,6 +439,10 @@ <Project>{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}</Project> <Name>DotNetOpenAuth.OAuth2.Client.UI</Name> </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj"> + <Project>{CCF3728A-B3D7-404A-9BC6-75197135F2D7}</Project> + <Name>DotNetOpenAuth.OAuth2.ClientAuthorization</Name> + </ProjectReference> <ProjectReference Include="..\DotNetOpenAuth.OAuth2.Client\DotNetOpenAuth.OAuth2.Client.csproj"> <Project>{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}</Project> <Name>DotNetOpenAuth.OAuth2.Client</Name> diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs index a767d53..5c3870c 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.Test.Messaging { using System.Diagnostics; using System.IO; using System.Net; + using System.Text; using System.Text.RegularExpressions; using System.Web; using DotNetOpenAuth.Messaging; @@ -229,6 +230,27 @@ namespace DotNetOpenAuth.Test.Messaging { Assert.AreEqual(PlainText, roundTripped); } + [Test] + public void SerializeAsJsonTest() { + var message = new TestMessageWithDate() { + Age = 18, + Timestamp = DateTime.Parse("4/28/2012"), + Name = "Andrew", + }; + string json = MessagingUtilities.SerializeAsJson(message, this.MessageDescriptions); + Assert.That(json, Is.EqualTo("{\"ts\":\"2012-04-28T00:00:00Z\",\"age\":18,\"Name\":\"Andrew\"}")); + } + + [Test] + public void DeserializeFromJson() { + var message = new TestMessageWithDate(); + string json = "{\"ts\":\"2012-04-28T00:00:00Z\",\"age\":18,\"Name\":\"Andrew\"}"; + MessagingUtilities.DeserializeFromJson(Encoding.UTF8.GetBytes(json), message, this.MessageDescriptions); + Assert.That(message.Age, Is.EqualTo(18)); + Assert.That(message.Timestamp, Is.EqualTo(DateTime.Parse("4/28/2012"))); + Assert.That(message.Name, Is.EqualTo("Andrew")); + } + /// <summary> /// Verifies that the time-independent string equality check works accurately. /// </summary> diff --git a/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs index 4d107c8..c519680 100644 --- a/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.Test.Messaging { Assert.AreSame(message, ex.FaultedMessage); } - [Test, ExpectedException(typeof(ArgumentNullException))] + [Test] public void CtorWithNullProtocolMessage() { new ProtocolException("message", (IProtocolMessage)null); } diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs index d7205d6..2e09943 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs @@ -9,10 +9,10 @@ namespace DotNetOpenAuth.Test.Mocks { using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; + using System.Net; using System.Text; using System.Threading; using System.Web; - using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.Test.OpenId; @@ -65,9 +65,17 @@ namespace DotNetOpenAuth.Test.Mocks { /// </summary> private IDictionary<string, string> incomingMessage; + /// <summary> + /// The recipient URL of the <see cref="incomingMessage"/>, where applicable. + /// </summary> private MessageReceivingEndpoint incomingMessageRecipient; /// <summary> + /// The headers of the <see cref="incomingMessage"/>, where applicable. + /// </summary> + private WebHeaderCollection incomingMessageHttpHeaders; + + /// <summary> /// A delegate that gets a chance to peak at and fiddle with all /// incoming messages. /// </summary> @@ -145,17 +153,27 @@ namespace DotNetOpenAuth.Test.Mocks { this.incomingMessage = this.MessageDescriptions.GetAccessor(message).Serialize(); var directedMessage = message as IDirectedProtocolMessage; this.incomingMessageRecipient = (directedMessage != null && directedMessage.Recipient != null) ? new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods) : null; + var httpMessage = message as IHttpDirectRequest; + this.incomingMessageHttpHeaders = (httpMessage != null) ? httpMessage.Headers.Clone() : null; this.incomingMessageSignal.Set(); } protected internal override HttpRequestBase GetRequestFromContext() { MessageReceivingEndpoint recipient; - var messageData = this.AwaitIncomingMessage(out recipient); + WebHeaderCollection headers; + var messageData = this.AwaitIncomingMessage(out recipient, out headers); + CoordinatingHttpRequestInfo result; if (messageData != null) { - return new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient); + result = new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient); } else { - return new CoordinatingHttpRequestInfo(recipient); + result = new CoordinatingHttpRequestInfo(recipient); + } + + if (headers != null) { + headers.ApplyTo(result.Headers); } + + return result; } protected override IProtocolMessage RequestCore(IDirectedProtocolMessage request) { @@ -166,7 +184,8 @@ namespace DotNetOpenAuth.Test.Mocks { // Now wait for a response... MessageReceivingEndpoint recipient; - IDictionary<string, string> responseData = this.AwaitIncomingMessage(out recipient); + WebHeaderCollection headers; + IDictionary<string, string> responseData = this.AwaitIncomingMessage(out recipient, out headers); ErrorUtilities.VerifyInternal(recipient == null, "The recipient is expected to be null for direct responses."); // And deserialize it. @@ -177,6 +196,10 @@ namespace DotNetOpenAuth.Test.Mocks { var responseAccessor = this.MessageDescriptions.GetAccessor(responseMessage); responseAccessor.Deserialize(responseData); + var responseMessageHttpRequest = responseMessage as IHttpDirectRequest; + if (headers != null && responseMessageHttpRequest != null) { + headers.ApplyTo(responseMessageHttpRequest.Headers); + } this.ProcessMessageFilter(responseMessage, false); return responseMessage; @@ -258,7 +281,7 @@ namespace DotNetOpenAuth.Test.Mocks { return channel.MessageFactoryTestHook; } - private IDictionary<string, string> AwaitIncomingMessage(out MessageReceivingEndpoint recipient) { + private IDictionary<string, string> AwaitIncomingMessage(out MessageReceivingEndpoint recipient, out WebHeaderCollection headers) { // Special care should be taken so that we don't indefinitely // wait for a message that may never come due to a bug in the product // or the test. @@ -284,8 +307,10 @@ namespace DotNetOpenAuth.Test.Mocks { this.waitingForMessage = false; var response = this.incomingMessage; recipient = this.incomingMessageRecipient; + headers = this.incomingMessageHttpHeaders; this.incomingMessage = null; this.incomingMessageRecipient = null; + this.incomingMessageHttpHeaders = null; // Briefly signal to another thread that might be waiting for our inbox to be empty this.messageReceivedSignal.Set(); diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs index 9f139f3..a1f5cf5 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs @@ -6,9 +6,10 @@ namespace DotNetOpenAuth.Test.Mocks { using System; - using System.Collections.Generic; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Messaging; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Net; +using DotNetOpenAuth.Messaging; internal class CoordinatingHttpRequestInfo : HttpRequestInfo { private readonly Channel channel; diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2AuthServerChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2AuthServerChannel.cs index 2b087fd..463b149 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2AuthServerChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2AuthServerChannel.cs @@ -21,8 +21,13 @@ namespace DotNetOpenAuth.Test.Mocks { this.wrappedChannel = (OAuth2AuthorizationServerChannel)wrappedChannel; } - public IAuthorizationServer AuthorizationServer { + public IAuthorizationServerHost AuthorizationServer { get { return this.wrappedChannel.AuthorizationServer; } } + + public IScopeSatisfiedCheck ScopeSatisfiedCheck { + get { return this.wrappedChannel.ScopeSatisfiedCheck; } + set { this.wrappedChannel.ScopeSatisfiedCheck = value; } + } } } diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs new file mode 100644 index 0000000..52f381d --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="CoordinatingOAuth2ClientChannel.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth2.ChannelElements; + + internal class CoordinatingOAuth2ClientChannel : CoordinatingChannel, IOAuth2ChannelWithClient { + private OAuth2ClientChannel wrappedChannel; + + internal CoordinatingOAuth2ClientChannel(Channel wrappedChannel, Action<IProtocolMessage> incomingMessageFilter, Action<IProtocolMessage> outgoingMessageFilter) + : base(wrappedChannel, incomingMessageFilter, outgoingMessageFilter) { + this.wrappedChannel = (OAuth2ClientChannel)wrappedChannel; + } + + public string ClientIdentifier { + get { return this.wrappedChannel.ClientIdentifier; } + set { this.wrappedChannel.ClientIdentifier = value; } + } + + public DotNetOpenAuth.OAuth2.ClientCredentialApplicator ClientCredentialApplicator { + get { return this.wrappedChannel.ClientCredentialApplicator; } + set { this.wrappedChannel.ClientCredentialApplicator = value; } + } + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessageWithDate.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessageWithDate.cs new file mode 100644 index 0000000..b0b89a0 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/TestMessageWithDate.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="TestMessageWithDate.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + internal class TestMessageWithDate : TestBaseMessage { + [MessagePart("ts", IsRequired = true)] + internal DateTime Timestamp { get; set; } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs b/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs index f3d8feb..3791e28 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { AuthorizationServerMock, new UserAgentClient(AuthorizationServerDescription), client => { - var request = new AccessTokenAuthorizationCodeRequest(AuthorizationServerDescription) + var request = new AccessTokenAuthorizationCodeRequestC(AuthorizationServerDescription) { ClientIdentifier = ClientId, ClientSecret = ClientSecret, AuthorizationCode = "foo" }; var response = client.Channel.Request<AccessTokenFailedResponse>(request); diff --git a/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs b/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs index bec85e2..52b5371 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/MessageFactoryTests.cs @@ -17,18 +17,22 @@ namespace DotNetOpenAuth.Test.OAuth2 { using NUnit.Framework; /// <summary> - /// Verifies that the WRAP message types are recognized. + /// Verifies that the OAuth 2 message types are recognized. /// </summary> public class MessageFactoryTests : OAuth2TestBase { private readonly MessageReceivingEndpoint recipient = new MessageReceivingEndpoint("http://who", HttpDeliveryMethods.PostRequest); - private OAuth2AuthorizationServerChannel channel; - private IMessageFactory messageFactory; + private IMessageFactory authServerMessageFactory; + + private IMessageFactory clientMessageFactory; public override void SetUp() { base.SetUp(); - this.channel = new OAuth2AuthorizationServerChannel(new Mock<IAuthorizationServer>().Object); - this.messageFactory = this.channel.MessageFactoryTestHook; + var authServerChannel = new OAuth2AuthorizationServerChannel(new Mock<IAuthorizationServerHost>().Object, new Mock<ClientAuthenticationModule>().Object); + this.authServerMessageFactory = authServerChannel.MessageFactoryTestHook; + + var clientChannel = new OAuth2ClientChannel(); + this.clientMessageFactory = clientChannel.MessageFactoryTestHook; } #region End user authorization messages @@ -40,7 +44,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.client_id, "abc" }, { Protocol.redirect_uri, "abc" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(EndUserAuthorizationRequest))); } @@ -51,7 +55,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.client_id, "abc" }, { Protocol.redirect_uri, "abc" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(EndUserAuthorizationImplicitRequest))); } @@ -60,7 +64,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { var fields = new Dictionary<string, string> { { Protocol.code, "abc" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.clientMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(EndUserAuthorizationSuccessResponseBase))); } @@ -70,7 +74,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.access_token, "abc" }, { Protocol.token_type, "bearer" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.clientMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(EndUserAuthorizationSuccessResponseBase))); } @@ -79,7 +83,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { var fields = new Dictionary<string, string> { { Protocol.error, "access-denied" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.clientMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(EndUserAuthorizationFailedResponse))); } @@ -94,7 +98,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.refresh_token, "abc" }, { Protocol.grant_type, "refresh-token" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(AccessTokenRefreshRequest))); } @@ -106,7 +110,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.grant_type, "authorization-code" }, { Protocol.redirect_uri, "http://someUri" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(AccessTokenAuthorizationCodeRequest))); } @@ -119,7 +123,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.username, "abc" }, { Protocol.password, "abc" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(AccessTokenResourceOwnerPasswordCredentialsRequest))); } @@ -130,7 +134,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { { Protocol.client_secret, "abc" }, { Protocol.grant_type, "none" }, }; - IDirectedProtocolMessage request = this.messageFactory.GetNewRequestMessage(this.recipient, fields); + IDirectedProtocolMessage request = this.authServerMessageFactory.GetNewRequestMessage(this.recipient, fields); Assert.That(request, Is.InstanceOf(typeof(AccessTokenClientCredentialsRequest))); } diff --git a/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs b/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs index 993cad5..6494585 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/OAuth2Coordinator.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { using System; using System.Collections.Generic; using System.Linq; + using System.Net; using System.Text; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.Test.Mocks; @@ -15,12 +16,12 @@ namespace DotNetOpenAuth.Test.OAuth2 { internal class OAuth2Coordinator<TClient> : CoordinatorBase<TClient, AuthorizationServer> where TClient : ClientBase { private readonly AuthorizationServerDescription serverDescription; - private readonly IAuthorizationServer authServerHost; + private readonly IAuthorizationServerHost authServerHost; private readonly TClient client; internal OAuth2Coordinator( AuthorizationServerDescription serverDescription, - IAuthorizationServer authServerHost, + IAuthorizationServerHost authServerHost, TClient client, Action<TClient> clientAction, Action<AuthorizationServer> authServerAction) @@ -34,13 +35,13 @@ namespace DotNetOpenAuth.Test.OAuth2 { this.client = client; this.client.ClientIdentifier = OAuth2TestBase.ClientId; - this.client.ClientSecret = OAuth2TestBase.ClientSecret; + this.client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(OAuth2TestBase.ClientSecret); } internal override void Run() { var authServer = new AuthorizationServer(this.authServerHost); - var rpCoordinatingChannel = new CoordinatingChannel(this.client.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter); + var rpCoordinatingChannel = new CoordinatingOAuth2ClientChannel(this.client.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter); var opCoordinatingChannel = new CoordinatingOAuth2AuthServerChannel(authServer.Channel, this.IncomingMessageFilter, this.OutgoingMessageFilter); rpCoordinatingChannel.RemoteChannel = opCoordinatingChannel; opCoordinatingChannel.RemoteChannel = rpCoordinatingChannel; diff --git a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs index 87d91f7..f43a349 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs @@ -13,6 +13,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.OAuth2.ChannelElements; + using DotNetOpenAuth.OAuth2.Messages; using Moq; public class OAuth2TestBase : TestBase { @@ -38,10 +39,10 @@ namespace DotNetOpenAuth.Test.OAuth2 { ClientCallback, ClientType.Confidential); - protected static readonly IAuthorizationServer AuthorizationServerMock = CreateAuthorizationServerMock().Object; + protected static readonly IAuthorizationServerHost AuthorizationServerMock = CreateAuthorizationServerMock().Object; - protected static Mock<IAuthorizationServer> CreateAuthorizationServerMock() { - var authHostMock = new Mock<IAuthorizationServer>(); + protected static Mock<IAuthorizationServerHost> CreateAuthorizationServerMock() { + var authHostMock = new Mock<IAuthorizationServerHost>(); var cryptoStore = new MemoryCryptoKeyStore(); authHostMock.Setup(m => m.GetClient(ClientId)).Returns(ClientDescription); authHostMock.SetupGet(m => m.CryptoKeyStore).Returns(cryptoStore); @@ -52,7 +53,9 @@ namespace DotNetOpenAuth.Test.OAuth2 { d => d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername && MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true); - authHostMock.Setup(m => m.IsResourceOwnerCredentialValid(ResourceOwnerUsername, ResourceOwnerPassword)).Returns(true); + string canonicalUserName = ResourceOwnerUsername; + authHostMock.Setup(m => m.TryAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>(), out canonicalUserName)).Returns(true); + authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken())); return authHostMock; } } diff --git a/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs index 97c0f56..ae03b0c 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/UserAgentClientAuthorizeTests.cs @@ -73,7 +73,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { server.ApproveAuthorizationRequest(request, ResourceOwnerUsername); }); - coordinatorClient.ClientSecret = null; // implicit grant clients don't need a secret. + coordinatorClient.ClientCredentialApplicator = null; // implicit grant clients don't need a secret. coordinator.Run(); } } diff --git a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs index fe0abd2..f5d9b8c 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.OAuth2 { using System; using System.Collections.Generic; using System.Linq; + using System.Net; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2; @@ -42,13 +43,28 @@ namespace DotNetOpenAuth.Test.OAuth2 { coordinator.Run(); } - [Test] - public void ResourceOwnerPasswordCredentialGrant() { + [Theory] + public void ResourceOwnerPasswordCredentialGrant(bool anonymousClient) { + var authHostMock = CreateAuthorizationServerMock(); + if (anonymousClient) { + authHostMock.Setup( + m => + m.IsAuthorizationValid( + It.Is<IAuthorizationDescription>( + d => + d.ClientIdentifier == null && d.User == ResourceOwnerUsername && + MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true); + } + var coordinator = new OAuth2Coordinator<WebServerClient>( AuthorizationServerDescription, - AuthorizationServerMock, + authHostMock.Object, new WebServerClient(AuthorizationServerDescription), client => { + if (anonymousClient) { + client.ClientIdentifier = null; + } + var authState = client.ExchangeUserCredentialForToken(ResourceOwnerUsername, ResourceOwnerPassword, TestScopes); Assert.That(authState.AccessToken, Is.Not.Null.And.Not.Empty); Assert.That(authState.RefreshToken, Is.Not.Null.And.Not.Empty); @@ -65,6 +81,9 @@ namespace DotNetOpenAuth.Test.OAuth2 { authServer.Setup( a => a.IsAuthorizationValid(It.Is<IAuthorizationDescription>(d => d.User == null && d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))) .Returns(true); + authServer.Setup( + a => a.TryAuthorizeClientCredentialsGrant(It.Is<IAccessTokenRequest>(d => d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))) + .Returns(true); var coordinator = new OAuth2Coordinator<WebServerClient>( AuthorizationServerDescription, authServer.Object, diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs index 9592605..e9ff7a4 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { /// </summary> [Test] public void UnifyExtensionsAsSregWithSreg() { - var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns) { + var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.TypeUris.Standard) { Nickname = DemandLevel.Request, }; this.extensions.Add(sregInjected); @@ -100,7 +100,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { /// </summary> [Test] public void UnifyExtensionsAsSregWithBothSregAndAX() { - var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns) { + var sregInjected = new ClaimsRequest(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.TypeUris.Standard) { Nickname = DemandLevel.Request, }; this.extensions.Add(sregInjected); diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs index b5bcd7b..05ba3ad 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs @@ -88,7 +88,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { [Test] public void SpreadSregToAxNoOpIfOPSupportsSreg() { this.authReq.AddExtension(this.sreg); - this.InjectAdvertisedTypeUri(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + this.InjectAdvertisedTypeUri(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.TypeUris.Standard); ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.All); Assert.IsFalse(this.authReq.AppliedExtensions.OfType<FetchRequest>().Any()); } diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/SimpleRegistration/ClaimsResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/SimpleRegistration/ClaimsResponseTests.cs index 35bfc78..f898511 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/SimpleRegistration/ClaimsResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/SimpleRegistration/ClaimsResponseTests.cs @@ -19,7 +19,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { public class ClaimsResponseTests { [Test] public void EmptyMailAddress() { - ClaimsResponse response = new ClaimsResponse(Constants.sreg_ns); + ClaimsResponse response = new ClaimsResponse(Constants.TypeUris.Standard); response.Email = string.Empty; Assert.IsNull(response.MailAddress); } @@ -133,17 +133,17 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { [Test] public void ResponseAlternateTypeUriTests() { - var request = new ClaimsRequest(Constants.sreg_ns10); + var request = new ClaimsRequest(Constants.TypeUris.Variant10); request.Email = DemandLevel.Require; - var response = new ClaimsResponse(Constants.sreg_ns10); + var response = new ClaimsResponse(Constants.TypeUris.Variant10); response.Email = "a@b.com"; ExtensionTestUtilities.Roundtrip(Protocol.Default, new[] { request }, new[] { response }); } private ClaimsResponse GetFilledData() { - return new ClaimsResponse(Constants.sreg_ns) { + return new ClaimsResponse(Constants.TypeUris.Standard) { BirthDate = new DateTime(2005, 2, 3), Culture = new System.Globalization.CultureInfo("en-US"), Email = "a@b.com", diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/AnonymousRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/AnonymousRequestTests.cs index 9b39522..7310eb3 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/AnonymousRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/AnonymousRequestTests.cs @@ -5,6 +5,9 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId.Provider { + using System.IO; + using System.Runtime.Serialization; + using System.Runtime.Serialization.Formatters.Binary; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; @@ -33,5 +36,25 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { Assert.IsInstanceOf<IndirectSignedResponse>(anonReq.Response); Assert.IsNotInstanceOf<PositiveAssertionResponse>(anonReq.Response); } + + /// <summary> + /// Verifies that the AuthenticationRequest method is serializable. + /// </summary> + [Test] + public void Serializable() { + var op = CreateProvider(); + Protocol protocol = Protocol.V20; + var req = new SignedResponseRequest(protocol.Version, OPUri, AuthenticationRequestMode.Setup); + req.ReturnTo = RPUri; + var anonReq = new AnonymousRequest(op, req); + + MemoryStream ms = new MemoryStream(); + IFormatter formatter = new BinaryFormatter(); + formatter.Serialize(ms, anonReq); + + ms.Position = 0; + var req2 = (AnonymousRequest)formatter.Deserialize(ms); + Assert.That(req2, Is.Not.Null); + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs index 8cc7116..baf5377 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs @@ -6,6 +6,9 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { using System; + using System.IO; + using System.Runtime.Serialization; + using System.Runtime.Serialization.Formatters.Binary; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Messages; @@ -45,5 +48,26 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { Assert.AreEqual(immediateRequest.LocalIdentifier, setupRequestMessage.LocalIdentifier); Assert.AreEqual(immediateRequest.Version, setupRequestMessage.Version); } + + /// <summary> + /// Verifies that the AuthenticationRequest method is serializable. + /// </summary> + [Test] + public void Serializable() { + OpenIdProvider provider = this.CreateProvider(); + CheckIdRequest immediateRequest = new CheckIdRequest(Protocol.Default.Version, OPUri, DotNetOpenAuth.OpenId.AuthenticationRequestMode.Immediate); + immediateRequest.Realm = RPRealmUri; + immediateRequest.ReturnTo = RPUri; + immediateRequest.LocalIdentifier = "http://somebody"; + AuthenticationRequest request = new AuthenticationRequest(provider, immediateRequest); + + MemoryStream ms = new MemoryStream(); + IFormatter formatter = new BinaryFormatter(); + formatter.Serialize(ms, request); + + ms.Position = 0; + var req2 = (AuthenticationRequest)formatter.Deserialize(ms); + Assert.That(req2, Is.Not.Null); + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs index 08e5a46..657b942 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/IdentifierDiscoveryResultTests.cs @@ -188,7 +188,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { se = IdentifierDiscoveryResult.CreateForProviderIdentifier( OPUri, - new ProviderEndpointDescription(OPUri, new[] { Protocol.V20.ClaimedIdentifierServiceTypeURI, "http://someextension", Constants.sreg_ns }), + new ProviderEndpointDescription(OPUri, new[] { Protocol.V20.ClaimedIdentifierServiceTypeURI, "http://someextension", Constants.TypeUris.Standard }), null, null); Assert.IsTrue(se.IsExtensionSupported<ClaimsRequest>()); diff --git a/src/DotNetOpenAuth.Test/TestUtilities.cs b/src/DotNetOpenAuth.Test/TestUtilities.cs index cf9b5a3..a526f7f 100644 --- a/src/DotNetOpenAuth.Test/TestUtilities.cs +++ b/src/DotNetOpenAuth.Test/TestUtilities.cs @@ -7,16 +7,35 @@ namespace DotNetOpenAuth.Test { using System; using System.Collections.Generic; + using System.Collections.Specialized; using System.Linq; + using System.Net; using log4net; /// <summary> /// An assortment of methods useful for testing. /// </summary> - internal class TestUtilities { + internal static class TestUtilities { /// <summary> /// The logger that tests should use. /// </summary> internal static readonly ILog TestLogger = LogManager.GetLogger("DotNetOpenAuth.Test"); + + internal static void ApplyTo(this NameValueCollection source, NameValueCollection target) { + Requires.NotNull(source, "source"); + Requires.NotNull(target, "target"); + + foreach (string header in source) { + target[header] = source[header]; + } + } + + internal static T Clone<T>(this T source) where T : NameValueCollection, new() { + Requires.NotNull(source, "source"); + + var result = new T(); + ApplyTo(source, result); + return result; + } } } diff --git a/src/DotNetOpenAuth.TestWeb/Web.config b/src/DotNetOpenAuth.TestWeb/Web.config index ad45e93..5d3174c 100644 --- a/src/DotNetOpenAuth.TestWeb/Web.config +++ b/src/DotNetOpenAuth.TestWeb/Web.config @@ -8,49 +8,23 @@ \Windows\Microsoft.Net\Framework\v2.x\Config --> <configuration> - - - <configSections> - <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> - <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> - <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/> - <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> - <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" /> - <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" /> - <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" /> - <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" /> - </sectionGroup> - </sectionGroup> - </sectionGroup> - </configSections> - - - <appSettings/> - <connectionStrings/> - <system.web> - <!-- + <appSettings/> + <connectionStrings/> + <system.web> + <!-- Set compilation debug="true" to insert debugging symbols into the compiled page. Because this affects performance, set this value to true only during development. --> - <compilation debug="true"> - - <assemblies> - <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> - <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> - <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> - </assemblies> - - </compilation> - <!-- + <compilation debug="true" targetFramework="4.0"/> + <!-- The <authentication> section enables configuration of the security authentication mode used by ASP.NET to identify an incoming user. --> - <authentication mode="Forms" /> - <!-- + <authentication mode="Forms"/> + <!-- The <customErrors> section enables configuration of what to do if/when an unhandled error occurs during the execution of a request. Specifically, @@ -62,78 +36,13 @@ <error statusCode="404" redirect="FileNotFound.htm" /> </customErrors> --> - - - <pages> - <controls> - <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - </controls> - </pages> - - <httpHandlers> - <remove verb="*" path="*.asmx"/> - <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/> - </httpHandlers> - <httpModules> - <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - </httpModules> - - - </system.web> - - <system.codedom> - <compilers> - <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" - type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> - <providerOption name="CompilerVersion" value="v3.5"/> - <providerOption name="WarnAsError" value="false"/> - </compiler> - <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" warningLevel="4" - type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> - <providerOption name="CompilerVersion" value="v3.5"/> - <providerOption name="OptionInfer" value="true"/> - <providerOption name="WarnAsError" value="false"/> - </compiler> - </compilers> - </system.codedom> - - <!-- + <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/> + </system.web> + <!-- The system.webServer section is required for running ASP.NET AJAX under Internet Information Services 7.0. It is not necessary for previous version of IIS. --> - <system.webServer> - <validation validateIntegratedModeConfiguration="false"/> - <modules> - <remove name="ScriptModule" /> - <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - </modules> - <handlers> - <remove name="WebServiceHandlerFactory-Integrated"/> - <remove name="ScriptHandlerFactory" /> - <remove name="ScriptHandlerFactoryAppServices" /> - <remove name="ScriptResource" /> - <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" - type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" - type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> - <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> - </handlers> - </system.webServer> - - <runtime> - <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> - <dependentAssembly> - <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/> - <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/> - </dependentAssembly> - <dependentAssembly> - <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/> - <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/> - </dependentAssembly> - </assemblyBinding> - </runtime> - -</configuration> + <system.webServer> + <modules runAllManagedModulesForAllRequests="true"/> + </system.webServer> +</configuration>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index e0f4ebf..f4d0a29 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -1,25 +1,25 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 11 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20B5E173-C3C4-49F8-BD25-E69044075B4D}" ProjectSection(SolutionItems) = preProject - ..\build.proj = ..\build.proj - ..\projecttemplates\DotNetOpenAuth Starter Kits.vscontent = ..\projecttemplates\DotNetOpenAuth Starter Kits.vscontent ..\LICENSE.txt = ..\LICENSE.txt + ..\build.proj = ..\build.proj ..\doc\README.Bin.html = ..\doc\README.Bin.html ..\doc\README.html = ..\doc\README.html + ..\projecttemplates\DotNetOpenAuth Starter Kits.vscontent = ..\projecttemplates\DotNetOpenAuth Starter Kits.vscontent ..\samples\README.html = ..\samples\README.html EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F-24F4-4136-8741-6063D0D7A031}" ProjectSection(SolutionItems) = preProject - ..\doc\specs\draft-ietf-oauth-v2-23.txt = ..\doc\specs\draft-ietf-oauth-v2-23.txt - ..\doc\specs\draft-ietf-oauth-v2-bearer.htm = ..\doc\specs\draft-ietf-oauth-v2-bearer.htm - ..\doc\specs\draft-jones-json-web-token.htm = ..\doc\specs\draft-jones-json-web-token.htm ..\doc\specs\ICAM_OpenID20Profile.pdf = ..\doc\specs\ICAM_OpenID20Profile.pdf ..\doc\specs\OAuth Core 1.0.htm = ..\doc\specs\OAuth Core 1.0.htm ..\doc\specs\OAuth Core 1.0a (Draft 3).htm = ..\doc\specs\OAuth Core 1.0a (Draft 3).htm ..\doc\specs\OpenID OAuth Extension.htm = ..\doc\specs\OpenID OAuth Extension.htm + ..\doc\specs\draft-ietf-oauth-v2-23.txt = ..\doc\specs\draft-ietf-oauth-v2-23.txt + ..\doc\specs\draft-ietf-oauth-v2-bearer.htm = ..\doc\specs\draft-ietf-oauth-v2-bearer.htm + ..\doc\specs\draft-jones-json-web-token.htm = ..\doc\specs\draft-jones-json-web-token.htm ..\doc\specs\openid-attribute-exchange-1_0.html = ..\doc\specs\openid-attribute-exchange-1_0.html ..\doc\specs\openid-authentication-1_1.html = ..\doc\specs\openid-authentication-1_1.html ..\doc\specs\openid-authentication-2_0.html = ..\doc\specs\openid-authentication-2_0.html @@ -40,31 +40,45 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E9ED920D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Templates", "Project Templates", "{B9EB8729-4B54-4453-B089-FE6761BA3057}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Product", "Product", "{8D4236F7-C49B-49D3-BA71-6B86C9514BDE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenID", "OpenID", "{C7EF1823-3AA7-477E-8476-28929F5C05D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{9AF74F53-10F5-49A2-B747-87B97CD559D3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InfoCard", "InfoCard", "{529B4262-6B5A-4EF9-BD3B-1D29A2597B67}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth2", "OAuth2", "{238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Combinations", "Combinations", "{57A7DD35-666C-4FA3-9A1B-38961E50CA27}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth1", "OAuth1", "{2DA24D4F-6918-43CF-973C-BC9D818F8E90}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Test", "DotNetOpenAuth.Test\DotNetOpenAuth.Test.csproj", "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.ApplicationBlock", "..\samples\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj", "{AA78D112-D889-414B-A7D4-467B34C7B663}" EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "DotNetOpenAuth.TestWeb", "DotNetOpenAuth.TestWeb\", "{47A84EF7-68C3-4D47-926A-9CCEA6518531}" ProjectSection(WebsiteProperties) = preProject - TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5" - ProjectReferences = "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}|DotNetOpenAuth.Test.dll;{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}|DotNetOpenAuth.InfoCard.dll;{60426312-6AE5-4835-8667-37EDEA670222}|DotNetOpenAuth.Core.dll;{A288FCC8-6FCF-46DA-A45E-5F9281556361}|DotNetOpenAuth.OAuth.dll;{3896A32A-E876-4C23-B9B8-78E17D134CD3}|DotNetOpenAuth.OpenId.dll;{56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}|DotNetOpenAuth.OAuth2.dll;{26DC877F-5987-48DD-9DDB-E62F2DE0E150}|Org.Mentalis.Security.Cryptography.dll;{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}|Mono.Math.dll;{173E7B8D-E751-46E2-A133-F72297C0D2F4}|DotNetOpenAuth.Core.UI.dll;{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}|DotNetOpenAuth.InfoCard.UI.dll;{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}|DotNetOpenAuth.OAuth.Consumer.dll;{FED1923A-6D70-49B5-A37A-FB744FEC1C86}|DotNetOpenAuth.OAuth.ServiceProvider.dll;{99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}|DotNetOpenAuth.OAuth2.AuthorizationServer.dll;{CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}|DotNetOpenAuth.OAuth2.Client.dll;{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}|DotNetOpenAuth.OAuth2.Client.UI.dll;{A1A3150A-7B0E-4A34-8E35-045296CD3C76}|DotNetOpenAuth.OAuth2.ResourceServer.dll;{F8284738-3B5D-4733-A511-38C23F4A763F}|DotNetOpenAuth.OpenId.Provider.dll;{F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}|DotNetOpenAuth.OpenId.RelyingParty.dll;{9D0F8866-2131-4C2A-BC0E-16FEA5B50828}|DotNetOpenAuth.OpenId.Provider.UI.dll;{75E13AAE-7D51-4421-ABFD-3F3DC91F576E}|DotNetOpenAuth.OpenId.UI.dll;{1ED8D424-F8AB-4050-ACEB-F27F4F909484}|DotNetOpenAuth.OpenId.RelyingParty.UI.dll;{115217C5-22CD-415C-A292-0DD0238CDD89}|DotNetOpenAuth.OAuth.Common.dll;" - Debug.AspNetCompiler.VirtualPath = "/DotNetOpenAuth.TestWeb" + Debug.AspNetCompiler.Debug = "True" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.PhysicalPath = "DotNetOpenAuth.TestWeb\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\DotNetOpenAuth.TestWeb\" Debug.AspNetCompiler.Updateable = "false" - Debug.AspNetCompiler.ForceOverwrite = "true" - Debug.AspNetCompiler.FixedNames = "false" - Debug.AspNetCompiler.Debug = "True" - Release.AspNetCompiler.VirtualPath = "/DotNetOpenAuth.TestWeb" + Debug.AspNetCompiler.VirtualPath = "/DotNetOpenAuth.TestWeb" + DefaultWebSiteLanguage = "Visual C#" + ProjectReferences = "{f8284738-3b5d-4733-a511-38c23f4a763f}|DotNetOpenAuth.OpenId.Provider.dll;{60426312-6AE5-4835-8667-37EDEA670222}|DotNetOpenAuth.Core.dll;{3896A32A-E876-4C23-B9B8-78E17D134CD3}|DotNetOpenAuth.OpenId.dll;{26DC877F-5987-48DD-9DDB-E62F2DE0E150}|Org.Mentalis.Security.Cryptography.dll;{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}|Mono.Math.dll;" + Release.AspNetCompiler.Debug = "False" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.PhysicalPath = "DotNetOpenAuth.TestWeb\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\DotNetOpenAuth.TestWeb\" Release.AspNetCompiler.Updateable = "false" - Release.AspNetCompiler.ForceOverwrite = "true" - Release.AspNetCompiler.FixedNames = "false" - Release.AspNetCompiler.Debug = "False" - VWDPort = "5073" - DefaultWebSiteLanguage = "Visual C#" + Release.AspNetCompiler.VirtualPath = "/DotNetOpenAuth.TestWeb" StartServerOnDebug = "false" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" + VWDPort = "5073" EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdProviderWebForms", "..\samples\OpenIdProviderWebForms\OpenIdProviderWebForms.csproj", "{2A59DE0A-B76A-4B42-9A33-04D34548353D}" @@ -73,22 +87,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdProviderMvc", "..\sam EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "InfoCardRelyingParty", "..\samples\InfoCardRelyingParty\", "{6EB90284-BD15-461C-BBF2-131CF55F7C8B}" ProjectSection(WebsiteProperties) = preProject - TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5" - ProjectReferences = "{408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}|DotNetOpenAuth.InfoCard.dll;{60426312-6AE5-4835-8667-37EDEA670222}|DotNetOpenAuth.Core.dll;{173E7B8D-E751-46E2-A133-F72297C0D2F4}|DotNetOpenAuth.Core.UI.dll;{26DC877F-5987-48DD-9DDB-E62F2DE0E150}|Org.Mentalis.Security.Cryptography.dll;{F4CD3C04-6037-4946-B7A5-34BFC96A75D2}|Mono.Math.dll;{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}|DotNetOpenAuth.InfoCard.UI.dll;" - Debug.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty" + Debug.AspNetCompiler.Debug = "True" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.PhysicalPath = "..\samples\InfoCardRelyingParty\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\InfoCardRelyingParty\" Debug.AspNetCompiler.Updateable = "true" - Debug.AspNetCompiler.ForceOverwrite = "true" - Debug.AspNetCompiler.FixedNames = "false" - Debug.AspNetCompiler.Debug = "True" - Release.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty" + Debug.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty" + DefaultWebSiteLanguage = "Visual Basic" + ProjectReferences = "{60426312-6ae5-4835-8667-37edea670222}|DotNetOpenAuth.Core.dll;{173e7b8d-e751-46e2-a133-f72297c0d2f4}|DotNetOpenAuth.Core.UI.dll;{408d10b8-34ba-4cbd-b7aa-feb1907aba4c}|DotNetOpenAuth.InfoCard.dll;{e040eb58-b4d2-457b-a023-ae6ef3bd34de}|DotNetOpenAuth.InfoCard.UI.dll;" + Release.AspNetCompiler.Debug = "False" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.PhysicalPath = "..\samples\InfoCardRelyingParty\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\InfoCardRelyingParty\" Release.AspNetCompiler.Updateable = "true" - Release.AspNetCompiler.ForceOverwrite = "true" - Release.AspNetCompiler.FixedNames = "false" - Release.AspNetCompiler.Debug = "False" + Release.AspNetCompiler.VirtualPath = "/InfoCardRelyingParty" + StartServerOnDebug = "false" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" VWDPort = "59719" EndProjectSection EndProject @@ -98,21 +114,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdRelyingPartyWebForms" EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OpenIdRelyingPartyClassicAsp", "..\samples\OpenIdRelyingPartyClassicAsp\", "{BBACD972-014D-478F-9B07-56B9E1D4CC73}" ProjectSection(WebsiteProperties) = preProject - TargetFrameworkMoniker = ".NETFramework,Version%3Dv3.5" - Debug.AspNetCompiler.VirtualPath = "/OpenIdRelyingPartyClassicAsp" + Debug.AspNetCompiler.Debug = "True" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.ForceOverwrite = "true" Debug.AspNetCompiler.PhysicalPath = "..\samples\OpenIdRelyingPartyClassicAsp\" Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\OpenIdRelyingPartyClassicAsp\" Debug.AspNetCompiler.Updateable = "true" - Debug.AspNetCompiler.ForceOverwrite = "true" - Debug.AspNetCompiler.FixedNames = "false" - Debug.AspNetCompiler.Debug = "True" - Release.AspNetCompiler.VirtualPath = "/OpenIdRelyingPartyClassicAsp" + Debug.AspNetCompiler.VirtualPath = "/OpenIdRelyingPartyClassicAsp" + Release.AspNetCompiler.Debug = "False" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.PhysicalPath = "..\samples\OpenIdRelyingPartyClassicAsp\" Release.AspNetCompiler.TargetPath = "PrecompiledWeb\OpenIdRelyingPartyClassicAsp\" Release.AspNetCompiler.Updateable = "true" - Release.AspNetCompiler.ForceOverwrite = "true" - Release.AspNetCompiler.FixedNames = "false" - Release.AspNetCompiler.Debug = "False" + Release.AspNetCompiler.VirtualPath = "/OpenIdRelyingPartyClassicAsp" + StartServerOnDebug = "false" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" VWDPort = "10318" EndProjectSection EndProject @@ -124,10 +141,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebFormsRelyingParty", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyLogic", "..\projecttemplates\RelyingPartyLogic\RelyingPartyLogic.csproj", "{17932639-1F50-48AF-B0A5-E2BF832F82CC}" ProjectSection(ProjectDependencies) = postProject - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} = {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} + {08A938B6-EBBD-4036-880E-CE7BA2D14510} = {08A938B6-EBBD-4036-880E-CE7BA2D14510} EndProjectSection EndProject -Project("{C8D11400-126E-41CD-887F-60BD40844F9E}") = "RelyingPartyDatabase", "..\projecttemplates\RelyingPartyDatabase\RelyingPartyDatabase.dbproj", "{2B4261AC-25AC-4B8D-B459-1C42B6B1401D}" +Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "RelyingPartyDatabase", "..\projecttemplates\RelyingPartyDatabase\RelyingPartyDatabase.sqlproj", "{08A938B6-EBBD-4036-880E-CE7BA2D14510}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcRelyingParty", "..\projecttemplates\MvcRelyingParty\MvcRelyingParty.csproj", "{152B7BAB-E884-4A59-8067-440971A682B3}" EndProject @@ -172,18 +189,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenId.UI", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Core.UI", "DotNetOpenAuth.Core.UI\DotNetOpenAuth.Core.UI.csproj", "{173E7B8D-E751-46E2-A133-F72297C0D2F4}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Product", "Product", "{8D4236F7-C49B-49D3-BA71-6B86C9514BDE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenID", "OpenID", "{C7EF1823-3AA7-477E-8476-28929F5C05D2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth", "OAuth", "{9AF74F53-10F5-49A2-B747-87B97CD559D3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InfoCard", "InfoCard", "{529B4262-6B5A-4EF9-BD3B-1D29A2597B67}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.InfoCard.UI", "DotNetOpenAuth.InfoCard.UI\DotNetOpenAuth.InfoCard.UI.csproj", "{E040EB58-B4D2-457B-A023-AE6EF3BD34DE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth2", "OAuth2", "{238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.Consumer", "DotNetOpenAuth.OAuth.Consumer\DotNetOpenAuth.OAuth.Consumer.csproj", "{B202E40D-4663-4A2B-ACDA-865F88FF7CAA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.ServiceProvider", "DotNetOpenAuth.OAuth.ServiceProvider\DotNetOpenAuth.OAuth.ServiceProvider.csproj", "{FED1923A-6D70-49B5-A37A-FB744FEC1C86}" @@ -196,12 +203,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.Resou EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.Client.UI", "DotNetOpenAuth.OAuth2.Client.UI\DotNetOpenAuth.OAuth2.Client.UI.csproj", "{ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Combinations", "Combinations", "{57A7DD35-666C-4FA3-9A1B-38961E50CA27}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OpenIdInfoCard.UI", "DotNetOpenAuth.OpenIdInfoCard.UI\DotNetOpenAuth.OpenIdInfoCard.UI.csproj", "{3A8347E8-59A5-4092-8842-95C75D7D2F36}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OAuth1", "OAuth1", "{2DA24D4F-6918-43CF-973C-BC9D818F8E90}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthConsumer", "..\samples\OAuthConsumer\OAuthConsumer.csproj", "{2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthServiceProvider", "..\samples\OAuthServiceProvider\OAuthServiceProvider.csproj", "{CAA2408C-6918-4902-A512-58BCD62216C3}" @@ -214,6 +217,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.AspNet.Test" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.Common", "DotNetOpenAuth.OAuth.Common\DotNetOpenAuth.OAuth.Common.csproj", "{115217C5-22CD-415C-A292-0DD0238CDD89}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.ClientAuthorization", "DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj", "{CCF3728A-B3D7-404A-9BC6-75197135F2D7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeAnalysis|Any CPU = CodeAnalysis|Any CPU @@ -221,356 +226,343 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Release|Any CPU.Build.0 = Release|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.Build.0 = Release|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.Build.0 = Debug|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.Build.0 = Release|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.Build.0 = Release|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.Build.0 = Debug|Any CPU - {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Release|Any CPU.ActiveCfg = Release|Any CPU {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB}.Release|Any CPU.Build.0 = Release|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Release|Any CPU.Build.0 = Release|Any CPU + {08A938B6-EBBD-4036-880E-CE7BA2D14510}.Release|Any CPU.Deploy.0 = Release|Any CPU + {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Release|Any CPU.Build.0 = Release|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {115217C5-22CD-415C-A292-0DD0238CDD89}.Release|Any CPU.Build.0 = Release|Any CPU + {152B7BAB-E884-4A59-8067-440971A682B3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {152B7BAB-E884-4A59-8067-440971A682B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {152B7BAB-E884-4A59-8067-440971A682B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {152B7BAB-E884-4A59-8067-440971A682B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {152B7BAB-E884-4A59-8067-440971A682B3}.Release|Any CPU.Build.0 = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.Build.0 = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.Build.0 = Release|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E8AEA89-BF69-47A1-B290-E8B0FE588700}.Release|Any CPU.Build.0 = Release|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Release|Any CPU.Build.0 = Debug|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.Build.0 = Release|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.Build.0 = Release|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.Build.0 = Release|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.Build.0 = Release|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.Deploy.0 = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.Build.0 = Release|Any CPU - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {152B7BAB-E884-4A59-8067-440971A682B3}.Release|Any CPU.Build.0 = Release|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Release|Any CPU.Build.0 = Release|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4}.Release|Any CPU.Build.0 = Release|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.Build.0 = Release|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.Build.0 = Release|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.Build.0 = Release|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Release|Any CPU.Build.0 = Release|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.Build.0 = Release|Any CPU - {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.Build.0 = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.Build.0 = Release|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A59DE0A-B76A-4B42-9A33-04D34548353D}.Release|Any CPU.Build.0 = Release|Any CPU + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Release|Any CPU.Build.0 = Release|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {3896A32A-E876-4C23-B9B8-78E17D134CD3}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Debug|Any CPU.Build.0 = Debug|Any CPU {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Release|Any CPU.ActiveCfg = Release|Any CPU {3896A32A-E876-4C23-B9B8-78E17D134CD3}.Release|Any CPU.Build.0 = Release|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.Build.0 = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.Build.0 = Release|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}.Release|Any CPU.Build.0 = Release|Any CPU + {47A84EF7-68C3-4D47-926A-9CCEA6518531}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {47A84EF7-68C3-4D47-926A-9CCEA6518531}.Release|Any CPU.Build.0 = Debug|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Release|Any CPU.Build.0 = Release|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.Build.0 = Release|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6}.Release|Any CPU.Build.0 = Release|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.Build.0 = Release|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.Build.0 = Release|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.Build.0 = Release|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26DC877F-5987-48DD-9DDB-E62F2DE0E150}.Release|Any CPU.Build.0 = Release|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1ED8D424-F8AB-4050-ACEB-F27F4F909484}.Release|Any CPU.Build.0 = Release|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.Build.0 = Release|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.Build.0 = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60426312-6AE5-4835-8667-37EDEA670222}.Release|Any CPU.Build.0 = Release|Any CPU + {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {6EB90284-BD15-461C-BBF2-131CF55F7C8B}.Release|Any CPU.Build.0 = Debug|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EC36418-DBC5-4AD1-A402-413604AA7A08}.Release|Any CPU.Build.0 = Release|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Debug|Any CPU.Build.0 = Debug|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Release|Any CPU.ActiveCfg = Release|Any CPU {75E13AAE-7D51-4421-ABFD-3F3DC91F576E}.Release|Any CPU.Build.0 = Release|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {173E7B8D-E751-46E2-A133-F72297C0D2F4}.Release|Any CPU.Build.0 = Release|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.Build.0 = Release|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.Build.0 = Release|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.Build.0 = Release|Any CPU + {9529606E-AF76-4387-BFB7-3D10A5B399AA}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9529606E-AF76-4387-BFB7-3D10A5B399AA}.Release|Any CPU.Build.0 = Release|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6}.Release|Any CPU.Build.0 = Release|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.Build.0 = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828}.Release|Any CPU.Build.0 = Release|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1A3150A-7B0E-4A34-8E35-045296CD3C76}.Release|Any CPU.Build.0 = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A288FCC8-6FCF-46DA-A45E-5F9281556361}.Release|Any CPU.Build.0 = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.Build.0 = Release|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA78D112-D889-414B-A7D4-467B34C7B663}.Release|Any CPU.Build.0 = Release|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Debug|Any CPU.Build.0 = Debug|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B}.Release|Any CPU.Build.0 = Release|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A8347E8-59A5-4092-8842-95C75D7D2F36}.Release|Any CPU.Build.0 = Release|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D}.Release|Any CPU.Build.0 = Release|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAA2408C-6918-4902-A512-58BCD62216C3}.Release|Any CPU.Build.0 = Release|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB}.Release|Any CPU.Build.0 = Release|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51835086-9611-4C53-819B-F2D5C9320873}.Release|Any CPU.Build.0 = Release|Any CPU + {AEA29D4D-396F-47F6-BC81-B58D4B855245}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEA29D4D-396F-47F6-BC81-B58D4B855245}.Release|Any CPU.Build.0 = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA}.Release|Any CPU.Build.0 = Release|Any CPU + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942}.Release|Any CPU.Build.0 = Release|Any CPU + {BBACD972-014D-478F-9B07-56B9E1D4CC73}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {BBACD972-014D-478F-9B07-56B9E1D4CC73}.Release|Any CPU.Build.0 = Debug|Any CPU {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Debug|Any CPU.Build.0 = Debug|Any CPU {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Release|Any CPU.ActiveCfg = Release|Any CPU {C23B217B-4D35-4A72-A1F7-FAEB4F39CB91}.Release|Any CPU.Build.0 = Release|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {115217C5-22CD-415C-A292-0DD0238CDD89}.Release|Any CPU.Build.0 = Release|Any CPU + {C78E8235-1D46-43EB-A912-80B522C4E9AE}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C78E8235-1D46-43EB-A912-80B522C4E9AE}.Release|Any CPU.Build.0 = Release|Any CPU + {CAA2408C-6918-4902-A512-58BCD62216C3}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {CAA2408C-6918-4902-A512-58BCD62216C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAA2408C-6918-4902-A512-58BCD62216C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAA2408C-6918-4902-A512-58BCD62216C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAA2408C-6918-4902-A512-58BCD62216C3}.Release|Any CPU.Build.0 = Release|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Release|Any CPU.Build.0 = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99}.Release|Any CPU.Build.0 = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E135F455-0669-49F8-9207-07FCA8C8FC79}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E135F455-0669-49F8-9207-07FCA8C8FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E135F455-0669-49F8-9207-07FCA8C8FC79}.Release|Any CPU.Build.0 = Release|Any CPU + {F289B925-4307-4BEC-B411-885CE70E3379}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {F289B925-4307-4BEC-B411-885CE70E3379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F289B925-4307-4BEC-B411-885CE70E3379}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F289B925-4307-4BEC-B411-885CE70E3379}.Release|Any CPU.Build.0 = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B}.Release|Any CPU.Build.0 = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2}.Release|Any CPU.Build.0 = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8284738-3B5D-4733-A511-38C23F4A763F}.Release|Any CPU.Build.0 = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FED1923A-6D70-49B5-A37A-FB744FEC1C86}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {CD57219F-24F4-4136-8741-6063D0D7A031} = {20B5E173-C3C4-49F8-BD25-E69044075B4D} {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {AA78D112-D889-414B-A7D4-467B34C7B663} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {2DA24D4F-6918-43CF-973C-BC9D818F8E90} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {2A59DE0A-B76A-4B42-9A33-04D34548353D} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {AEA29D4D-396F-47F6-BC81-B58D4B855245} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {1E8AEA89-BF69-47A1-B290-E8B0FE588700} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {BBACD972-014D-478F-9B07-56B9E1D4CC73} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {08A938B6-EBBD-4036-880E-CE7BA2D14510} = {B9EB8729-4B54-4453-B089-FE6761BA3057} {0B4EB2A8-283D-48FB-BCD0-85B8DFFE05E4} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {F289B925-4307-4BEC-B411-885CE70E3379} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} - {6EC36418-DBC5-4AD1-A402-413604AA7A08} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} - {9529606E-AF76-4387-BFB7-3D10A5B399AA} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} - {E135F455-0669-49F8-9207-07FCA8C8FC79} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} - {C78E8235-1D46-43EB-A912-80B522C4E9AE} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} - {6EB90284-BD15-461C-BBF2-131CF55F7C8B} = {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277} - {5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D} - {A78F8FC6-7B03-4230-BE41-761E400D6810} = {B9EB8729-4B54-4453-B089-FE6761BA3057} - {17932639-1F50-48AF-B0A5-E2BF832F82CC} = {B9EB8729-4B54-4453-B089-FE6761BA3057} - {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {115217C5-22CD-415C-A292-0DD0238CDD89} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} {152B7BAB-E884-4A59-8067-440971A682B3} = {B9EB8729-4B54-4453-B089-FE6761BA3057} - {C7EF1823-3AA7-477E-8476-28929F5C05D2} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {9AF74F53-10F5-49A2-B747-87B97CD559D3} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} {173E7B8D-E751-46E2-A133-F72297C0D2F4} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {60426312-6AE5-4835-8667-37EDEA670222} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {57A7DD35-666C-4FA3-9A1B-38961E50CA27} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {51835086-9611-4C53-819B-F2D5C9320873} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {115217C5-22CD-415C-A292-0DD0238CDD89} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} - {F8284738-3B5D-4733-A511-38C23F4A763F} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {F4CD3C04-6037-4946-B7A5-34BFC96A75D2} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {26DC877F-5987-48DD-9DDB-E62F2DE0E150} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {17932639-1F50-48AF-B0A5-E2BF832F82CC} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} + {1E8AEA89-BF69-47A1-B290-E8B0FE588700} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} {1ED8D424-F8AB-4050-ACEB-F27F4F909484} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {9D0F8866-2131-4C2A-BC0E-16FEA5B50828} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {75E13AAE-7D51-4421-ABFD-3F3DC91F576E} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {26DC877F-5987-48DD-9DDB-E62F2DE0E150} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {2A59DE0A-B76A-4B42-9A33-04D34548353D} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D} = {2DA24D4F-6918-43CF-973C-BC9D818F8E90} + {2DA24D4F-6918-43CF-973C-BC9D818F8E90} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} {3896A32A-E876-4C23-B9B8-78E17D134CD3} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} - {A288FCC8-6FCF-46DA-A45E-5F9281556361} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} - {B202E40D-4663-4A2B-ACDA-865F88FF7CAA} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} - {FED1923A-6D70-49B5-A37A-FB744FEC1C86} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {3A8347E8-59A5-4092-8842-95C75D7D2F36} = {57A7DD35-666C-4FA3-9A1B-38961E50CA27} {408D10B8-34BA-4CBD-B7AA-FEB1907ABA4C} = {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} - {E040EB58-B4D2-457B-A023-AE6EF3BD34DE} = {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} + {4BFAA336-5DF3-4F27-82D3-06D13240E8AB} = {57A7DD35-666C-4FA3-9A1B-38961E50CA27} + {51835086-9611-4C53-819B-F2D5C9320873} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} {56459A6C-6BA2-4BAC-A9C0-27E3BD961FA6} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {57A7DD35-666C-4FA3-9A1B-38961E50CA27} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D} + {60426312-6AE5-4835-8667-37EDEA670222} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {6EB90284-BD15-461C-BBF2-131CF55F7C8B} = {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277} + {6EC36418-DBC5-4AD1-A402-413604AA7A08} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} + {75E13AAE-7D51-4421-ABFD-3F3DC91F576E} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} + {9529606E-AF76-4387-BFB7-3D10A5B399AA} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} {99BB7543-EA16-43EE-A7BC-D7A25A3B22F6} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} - {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {9AF74F53-10F5-49A2-B747-87B97CD559D3} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} + {9D0F8866-2131-4C2A-BC0E-16FEA5B50828} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} {A1A3150A-7B0E-4A34-8E35-045296CD3C76} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {A288FCC8-6FCF-46DA-A45E-5F9281556361} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {A78F8FC6-7B03-4230-BE41-761E400D6810} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {AA78D112-D889-414B-A7D4-467B34C7B663} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} {ADC2CC8C-541E-4F86-ACB1-DD504A36FA4B} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} - {3A8347E8-59A5-4092-8842-95C75D7D2F36} = {57A7DD35-666C-4FA3-9A1B-38961E50CA27} - {4BFAA336-5DF3-4F27-82D3-06D13240E8AB} = {57A7DD35-666C-4FA3-9A1B-38961E50CA27} - {2BF1FFD1-607E-40D0-8AB5-EDA677EF932D} = {2DA24D4F-6918-43CF-973C-BC9D818F8E90} + {AEA29D4D-396F-47F6-BC81-B58D4B855245} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {B202E40D-4663-4A2B-ACDA-865F88FF7CAA} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} + {B64A1E7E-6A15-4B91-AF13-7D48F7DA5942} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {BBACD972-014D-478F-9B07-56B9E1D4CC73} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {C78E8235-1D46-43EB-A912-80B522C4E9AE} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} + {C7EF1823-3AA7-477E-8476-28929F5C05D2} = {8D4236F7-C49B-49D3-BA71-6B86C9514BDE} {CAA2408C-6918-4902-A512-58BCD62216C3} = {2DA24D4F-6918-43CF-973C-BC9D818F8E90} + {CCF3728A-B3D7-404A-9BC6-75197135F2D7} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {CD57219F-24F4-4136-8741-6063D0D7A031} = {20B5E173-C3C4-49F8-BD25-E69044075B4D} + {CDEDD439-7F35-4E6E-8605-4E70BDC4CC99} = {238B6BA8-AD99-43C9-B8E2-D2BCE6CE04DC} + {E040EB58-B4D2-457B-A023-AE6EF3BD34DE} = {529B4262-6B5A-4EF9-BD3B-1D29A2597B67} + {E135F455-0669-49F8-9207-07FCA8C8FC79} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} + {F289B925-4307-4BEC-B411-885CE70E3379} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} + {F458AB60-BA1C-43D9-8CEF-EC01B50BE87B} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {F4CD3C04-6037-4946-B7A5-34BFC96A75D2} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {F8284738-3B5D-4733-A511-38C23F4A763F} = {C7EF1823-3AA7-477E-8476-28929F5C05D2} + {FED1923A-6D70-49B5-A37A-FB744FEC1C86} = {9AF74F53-10F5-49A2-B747-87B97CD559D3} EndGlobalSection EndGlobal diff --git a/src/version.txt b/src/version.txt index 0a076a9..9852b74 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1,3 +1,3 @@ -4.0.3 - -0.23.0-draft5 +4.1.0 +-beta +0.25.0-draft1 |