summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs7
-rw-r--r--src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd8
-rw-r--r--src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj1
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs1
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Channel.cs6
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs10
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs16
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs21
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs10
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs5
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs196
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs23
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs25
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs14
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs13
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs21
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs13
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj3
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs19
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedAuthorizationCheckResponse.cs40
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedUserAuthorizationCheckResponse.cs42
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs17
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs4
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs46
-rw-r--r--src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenResult.cs (renamed from src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs)13
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs25
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs6
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs8
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs42
-rw-r--r--src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs37
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj2
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs19
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs14
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs19
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs6
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs2
-rw-r--r--src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenResult.cs33
-rw-r--r--src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs2
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs15
-rw-r--r--src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs2
-rw-r--r--src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs1
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs12
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs19
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs21
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs4
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs8
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs23
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs183
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs5
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs4
-rw-r--r--src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs32
-rw-r--r--src/version.txt3
52 files changed, 932 insertions, 189 deletions
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs
index faea868..653a0b0 100644
--- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/MicrosoftClient.cs
@@ -71,6 +71,13 @@ namespace DotNetOpenAuth.AspNet.Clients {
#endregion
+ /// <summary>
+ /// Gets the identifier for this client as it is registered with Microsoft.
+ /// </summary>
+ protected string AppId {
+ get { return this.appId; }
+ }
+
#region Methods
/// <summary>
diff --git a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
index 8a970f4..0fbdd7f 100644
--- a/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth.Core/Configuration/DotNetOpenAuth.xsd
@@ -945,6 +945,14 @@
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
</xs:choice>
+ <xs:attribute name="maxAuthorizationTime" type="xs:string">
+ <xs:annotation>
+ <xs:documentation>
+ The maximum time a user can take at the authorization server before the client considers
+ the authorization lost.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="authorizationServer">
diff --git a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
index eb38711..dc47259 100644
--- a/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
+++ b/src/DotNetOpenAuth.Core/DotNetOpenAuth.Core.csproj
@@ -59,6 +59,7 @@
<Compile Include="Messaging\OutgoingWebResponseActionResult.cs" />
<Compile Include="Messaging\ProtocolFaultResponseException.cs" />
<Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" />
+ <Compile Include="Messaging\Reflection\IMessagePartFormattingEncoder.cs" />
<Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" />
<Compile Include="Messaging\Reflection\IMessagePartOriginalEncoder.cs" />
<Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" />
diff --git a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs
index 5126897..88b8fed 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Bindings/ExpiredMessageException.cs
@@ -22,7 +22,6 @@ namespace DotNetOpenAuth.Messaging.Bindings {
public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage)
: base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) {
Requires.True(utcExpirationDate.Kind == DateTimeKind.Utc, "utcExpirationDate");
- Requires.NotNull(faultedMessage, "faultedMessage");
}
/// <summary>
diff --git a/src/DotNetOpenAuth.Core/Messaging/Channel.cs b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
index 672a942..f8ac6a1 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Channel.cs
@@ -650,10 +650,12 @@ namespace DotNetOpenAuth.Messaging {
protected static bool HttpMethodHasEntity(string httpMethod) {
if (string.Equals(httpMethod, "GET", StringComparison.Ordinal) ||
string.Equals(httpMethod, "HEAD", StringComparison.Ordinal) ||
- string.Equals(httpMethod, "DELETE", StringComparison.Ordinal)) {
+ string.Equals(httpMethod, "DELETE", StringComparison.Ordinal) ||
+ string.Equals(httpMethod, "OPTIONS", StringComparison.Ordinal)) {
return false;
} else if (string.Equals(httpMethod, "POST", StringComparison.Ordinal) ||
- string.Equals(httpMethod, "PUT", StringComparison.Ordinal)) {
+ string.Equals(httpMethod, "PUT", StringComparison.Ordinal) ||
+ string.Equals(httpMethod, "PATCH", StringComparison.Ordinal)) {
return true;
} else {
throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod);
diff --git a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
index 2452502..e7ac254 100644
--- a/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/DataBagFormatterBase.cs
@@ -193,20 +193,18 @@ namespace DotNetOpenAuth.Messaging {
/// 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>
+ /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. May be null if no carrying message is applicable.</param>
+ /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation, but may be <c>null</c>.</param>
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No apparent problem. False positive?")]
- public void Deserialize(T message, IProtocolMessage containingMessage, string value, string messagePartName) {
+ public void Deserialize(T message, string value, IProtocolMessage containingMessage, string messagePartName) {
Requires.NotNull(message, "message");
- Requires.NotNull(containingMessage, "containingMessage");
Requires.NotNullOrEmpty(value, "value");
- Requires.NotNullOrEmpty(messagePartName, "messagePartName");
string symmetricSecretHandle = null;
if (this.encrypted && this.cryptoKeyStore != null) {
string valueWithoutHandle;
- MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, messagePartName, value, out symmetricSecretHandle, out valueWithoutHandle);
+ MessagingUtilities.ExtractKeyHandleAndPayload(messagePartName, value, out symmetricSecretHandle, out valueWithoutHandle);
value = valueWithoutHandle;
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs
index c92b9de..dbd3855 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpDeliveryMethods.cs
@@ -48,11 +48,21 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
/// </summary>
- HeadRequest = 0x20,
+ HeadRequest = 0x20,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary>
+ PatchRequest = 0x40,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary>
+ OptionsRequest = 0x80,
/// <summary>
/// The flags that control HTTP verbs.
- /// </summary>
- HttpVerbMask = PostRequest | GetRequest | PutRequest | DeleteRequest | HeadRequest,
+ /// </summary>
+ HttpVerbMask = PostRequest | GetRequest | PutRequest | DeleteRequest | HeadRequest | PatchRequest | OptionsRequest,
}
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
index 4b4a3fe..75da833 100644
--- a/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/HttpRequestInfo.cs
@@ -61,6 +61,11 @@ namespace DotNetOpenAuth.Messaging {
private readonly NameValueCollection serverVariables;
/// <summary>
+ /// The backing field for the <see cref="Cookies"/> property.
+ /// </summary>
+ private readonly HttpCookieCollection cookies;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
/// </summary>
/// <param name="request">The request.</param>
@@ -75,6 +80,7 @@ namespace DotNetOpenAuth.Messaging {
this.form = new NameValueCollection();
this.queryString = HttpUtility.ParseQueryString(requestUri.Query);
this.serverVariables = new NameValueCollection();
+ this.cookies = new HttpCookieCollection();
Reporting.RecordRequestStatistics(this);
}
@@ -86,7 +92,8 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="requestUri">The request URI.</param>
/// <param name="form">The form variables.</param>
/// <param name="headers">The HTTP headers.</param>
- internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null) {
+ /// <param name="cookies">The cookies in the request.</param>
+ internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null, HttpCookieCollection cookies = null) {
Requires.NotNullOrEmpty(httpMethod, "httpMethod");
Requires.NotNull(requestUri, "requestUri");
@@ -96,6 +103,7 @@ namespace DotNetOpenAuth.Messaging {
this.queryString = HttpUtility.ParseQueryString(requestUri.Query);
this.headers = headers ?? new WebHeaderCollection();
this.serverVariables = new NameValueCollection();
+ this.cookies = cookies ?? new HttpCookieCollection();
}
/// <summary>
@@ -111,6 +119,7 @@ namespace DotNetOpenAuth.Messaging {
this.headers = listenerRequest.Headers;
this.form = ParseFormData(listenerRequest.HttpMethod, listenerRequest.Headers, () => listenerRequest.InputStream);
this.serverVariables = new NameValueCollection();
+ this.cookies = new HttpCookieCollection();
Reporting.RecordRequestStatistics(this);
}
@@ -131,6 +140,7 @@ namespace DotNetOpenAuth.Messaging {
AddHeaders(this.headers, request.Content.Headers);
this.form = ParseFormData(this.httpMethod, this.headers, () => request.Content.ReadAsStreamAsync().Result);
this.serverVariables = new NameValueCollection();
+ this.cookies = new HttpCookieCollection();
Reporting.RecordRequestStatistics(this);
}
@@ -153,6 +163,7 @@ namespace DotNetOpenAuth.Messaging {
this.queryString = HttpUtility.ParseQueryString(requestUri.Query);
this.form = ParseFormData(httpMethod, headers, () => inputStream);
this.serverVariables = new NameValueCollection();
+ this.cookies = new HttpCookieCollection();
Reporting.RecordRequestStatistics(this);
}
@@ -207,6 +218,14 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets the collection of cookies that were sent by the client.
+ /// </summary>
+ /// <returns>The client's cookies.</returns>
+ public override HttpCookieCollection Cookies {
+ get { return this.cookies; }
+ }
+
+ /// <summary>
/// Creates an <see cref="HttpRequestBase"/> instance that describes the specified HTTP request.
/// </summary>
/// <param name="request">The request.</param>
diff --git a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
index 923773e..0d1ab03 100644
--- a/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/IDataBagFormatter.cs
@@ -25,10 +25,10 @@ namespace DotNetOpenAuth.Messaging {
/// 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>
- void Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName);
+ /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. May be null if no carrying message is applicable.</param>
+ /// <param name="messagePartName">The name of the parameter whose value is to be deserialized. Used for error message generation, but may be <c>null</c>.</param>
+ void Deserialize(T message, string data, IProtocolMessage containingMessage = null, string messagePartName = null);
}
/// <summary>
@@ -61,10 +61,10 @@ namespace DotNetOpenAuth.Messaging {
/// 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="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param>
/// <param name="messagePartName">Name of the message part whose value is to be deserialized. Used for exception messages.</param>
- void IDataBagFormatter<T>.Deserialize(T message, IProtocolMessage containingMessage, string data, string messagePartName) {
+ void IDataBagFormatter<T>.Deserialize(T message, string data, IProtocolMessage containingMessage, string messagePartName) {
Requires.NotNull(message, "message");
Requires.NotNull(containingMessage, "containingMessage");
Requires.NotNullOrEmpty(data, "data");
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
index 15df48a..7391867 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessageSerializer.cs
@@ -97,9 +97,10 @@ namespace DotNetOpenAuth.Messaging {
Contract.Assume(partDescription != null);
if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) {
include = true;
- if (IsNumeric(partDescription.MemberDeclaredType)) {
+ Type formattingType = partDescription.PreferredFormattingType;
+ if (IsNumeric(formattingType)) {
type = "number";
- } else if (partDescription.MemberDeclaredType.IsAssignableFrom(typeof(bool))) {
+ } else if (formattingType.IsAssignableFrom(typeof(bool))) {
type = "boolean";
}
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
index 7aa4469..80703c1 100644
--- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs
@@ -23,6 +23,7 @@ namespace DotNetOpenAuth.Messaging {
using System.Security;
using System.Security.Cryptography;
using System.Text;
+ using System.Threading;
using System.Web;
using System.Web.Mvc;
using System.Xml;
@@ -41,11 +42,6 @@ namespace DotNetOpenAuth.Messaging {
internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider();
/// <summary>
- /// A pseudo-random data generator (NOT cryptographically strong random data)
- /// </summary>
- internal static readonly Random NonCryptoRandomDataGenerator = new Random();
-
- /// <summary>
/// The uppercase alphabet.
/// </summary>
internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -155,6 +151,13 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets a random number generator for use on the current thread only.
+ /// </summary>
+ internal static Random NonCryptoRandomDataGenerator {
+ get { return ThreadSafeRandom.RandomNumberGenerator; }
+ }
+
+ /// <summary>
/// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
/// </summary>
/// <param name="response">The response to send to the user agent.</param>
@@ -370,6 +373,64 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets the public facing URL for the given incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The incoming request. Cannot be <c>null</c>.</param>
+ /// <param name="serverVariables">The server variables to consider part of the request. Cannot be <c>null</c>.</param>
+ /// <returns>
+ /// The URI that the outside world used to create this request.
+ /// </returns>
+ /// <remarks>
+ /// Although the <paramref name="serverVariables"/> value can be obtained from
+ /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
+ /// in so we can simulate injected values from our unit tests since the actual property
+ /// is a read-only kind of <see cref="NameValueCollection"/>.
+ /// </remarks>
+ public static Uri GetPublicFacingUrl(this HttpRequestBase request, NameValueCollection serverVariables) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(serverVariables, "serverVariables");
+
+ // Due to URL rewriting, cloud computing (i.e. Azure)
+ // and web farms, etc., we have to be VERY careful about what
+ // we consider the incoming URL. We want to see the URL as it would
+ // appear on the public-facing side of the hosting web site.
+ // HttpRequest.Url gives us the internal URL in a cloud environment,
+ // So we use a variable that (at least from what I can tell) gives us
+ // the public URL:
+ if (serverVariables["HTTP_HOST"] != null) {
+ ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
+ string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ??
+ (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme);
+ Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
+ UriBuilder publicRequestUri = new UriBuilder(request.Url);
+ publicRequestUri.Scheme = scheme;
+ publicRequestUri.Host = hostAndPort.Host;
+ publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
+ return publicRequestUri.Uri;
+ } else {
+ // Failover to the method that works for non-web farm enviroments.
+ // We use Request.Url for the full path to the server, and modify it
+ // with Request.RawUrl to capture both the cookieless session "directory" if it exists
+ // and the original path in case URL rewriting is going on. We don't want to be
+ // fooled by URL rewriting because we're comparing the actual URL with what's in
+ // the return_to parameter in some cases.
+ // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
+ // session, but not the URL rewriting problem.
+ return new Uri(request.Url, request.RawUrl);
+ }
+ }
+
+ /// <summary>
+ /// Gets the public facing URL for the given incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The incoming request. Cannot be <c>null</c>. Server variables are read from this request.</param>
+ /// <returns>The URI that the outside world used to create this request.</returns>
+ public static Uri GetPublicFacingUrl(this HttpRequestBase request) {
+ Requires.NotNull(request, "request");
+ return GetPublicFacingUrl(request, request.ServerVariables);
+ }
+
+ /// <summary>
/// Gets the URL to the root of a web site, which may include a virtual directory path.
/// </summary>
/// <returns>An absolute URI.</returns>
@@ -569,18 +630,15 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>.
/// </summary>
- /// <param name="containingMessage">The containing message.</param>
- /// <param name="messagePart">The message part.</param>
+ /// <param name="messagePart">The message part. May be null if not applicable.</param>
/// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param>
/// <param name="handle">The crypto key handle.</param>
/// <param name="dataBlob">The encrypted/signed data.</param>
- internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) {
- Requires.NotNull(containingMessage, "containingMessage");
- Requires.NotNullOrEmpty(messagePart, "messagePart");
+ internal static void ExtractKeyHandleAndPayload(string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) {
Requires.NotNullOrEmpty(keyHandleAndBlob, "keyHandleAndBlob");
int privateHandleIndex = keyHandleAndBlob.IndexOf('!');
- ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob);
+ ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart ?? "<unknown>", keyHandleAndBlob);
handle = keyHandleAndBlob.Substring(0, privateHandleIndex);
dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1);
}
@@ -608,7 +666,7 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets a cryptographically strong random sequence of values.
+ /// Gets a cryptographically strong random string of base64 characters.
/// </summary>
/// <param name="binaryLength">The length of the byte sequence to generate.</param>
/// <returns>A base64 encoding of the generated random data,
@@ -620,6 +678,18 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets a NON-cryptographically strong random string of base64 characters.
+ /// </summary>
+ /// <param name="binaryLength">The length of the byte sequence to generate.</param>
+ /// <returns>A base64 encoding of the generated random data,
+ /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns>
+ internal static string GetNonCryptoRandomDataAsBase64(int binaryLength) {
+ byte[] uniq_bytes = GetNonCryptoRandomData(binaryLength);
+ string uniq = Convert.ToBase64String(uniq_bytes);
+ return uniq;
+ }
+
+ /// <summary>
/// Gets a random string made up of a given set of allowable characters.
/// </summary>
/// <param name="length">The length of the desired random string.</param>
@@ -630,8 +700,9 @@ namespace DotNetOpenAuth.Messaging {
Requires.True(allowableCharacters != null && allowableCharacters.Length >= 2, "allowableCharacters");
char[] randomString = new char[length];
+ var random = NonCryptoRandomDataGenerator;
for (int i = 0; i < length; i++) {
- randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)];
+ randomString[i] = allowableCharacters[random.Next(allowableCharacters.Length)];
}
return new string(randomString);
@@ -1491,6 +1562,10 @@ namespace DotNetOpenAuth.Messaging {
return HttpDeliveryMethods.DeleteRequest;
} else if (httpVerb == "HEAD") {
return HttpDeliveryMethods.HeadRequest;
+ } else if (httpVerb == "PATCH") {
+ return HttpDeliveryMethods.PatchRequest;
+ } else if (httpVerb == "OPTIONS") {
+ return HttpDeliveryMethods.OptionsRequest;
} else {
throw ErrorUtilities.ThrowArgumentNamed("httpVerb", MessagingStrings.UnsupportedHttpVerb, httpVerb);
}
@@ -1500,7 +1575,7 @@ namespace DotNetOpenAuth.Messaging {
/// Gets the HTTP verb to use for a given <see cref="HttpDeliveryMethods"/> enum value.
/// </summary>
/// <param name="httpMethod">The HTTP method.</param>
- /// <returns>An HTTP verb, such as GET, POST, PUT, or DELETE.</returns>
+ /// <returns>An HTTP verb, such as GET, POST, PUT, DELETE, PATCH, or OPTION.</returns>
internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) {
if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.GetRequest) {
return "GET";
@@ -1512,6 +1587,10 @@ namespace DotNetOpenAuth.Messaging {
return "DELETE";
} else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.HeadRequest) {
return "HEAD";
+ } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.PatchRequest) {
+ return "PATCH";
+ } else if ((httpMethod & HttpDeliveryMethods.HttpVerbMask) == HttpDeliveryMethods.OptionsRequest) {
+ return "OPTIONS";
} else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) {
return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET.
} else {
@@ -1901,64 +1980,6 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
- /// Gets the public facing URL for the given incoming HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="serverVariables">The server variables to consider part of the request.</param>
- /// <returns>
- /// The URI that the outside world used to create this request.
- /// </returns>
- /// <remarks>
- /// Although the <paramref name="serverVariables"/> value can be obtained from
- /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them
- /// in so we can simulate injected values from our unit tests since the actual property
- /// is a read-only kind of <see cref="NameValueCollection"/>.
- /// </remarks>
- internal static Uri GetPublicFacingUrl(this HttpRequestBase request, NameValueCollection serverVariables) {
- Requires.NotNull(request, "request");
- Requires.NotNull(serverVariables, "serverVariables");
-
- // Due to URL rewriting, cloud computing (i.e. Azure)
- // and web farms, etc., we have to be VERY careful about what
- // we consider the incoming URL. We want to see the URL as it would
- // appear on the public-facing side of the hosting web site.
- // HttpRequest.Url gives us the internal URL in a cloud environment,
- // So we use a variable that (at least from what I can tell) gives us
- // the public URL:
- if (serverVariables["HTTP_HOST"] != null) {
- ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
- string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ??
- (string.Equals(serverVariables["HTTP_FRONT_END_HTTPS"], "on", StringComparison.OrdinalIgnoreCase) ? Uri.UriSchemeHttps : request.Url.Scheme);
- Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
- UriBuilder publicRequestUri = new UriBuilder(request.Url);
- publicRequestUri.Scheme = scheme;
- publicRequestUri.Host = hostAndPort.Host;
- publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
- return publicRequestUri.Uri;
- } else {
- // Failover to the method that works for non-web farm enviroments.
- // We use Request.Url for the full path to the server, and modify it
- // with Request.RawUrl to capture both the cookieless session "directory" if it exists
- // and the original path in case URL rewriting is going on. We don't want to be
- // fooled by URL rewriting because we're comparing the actual URL with what's in
- // the return_to parameter in some cases.
- // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
- // session, but not the URL rewriting problem.
- return new Uri(request.Url, request.RawUrl);
- }
- }
-
- /// <summary>
- /// Gets the public facing URL for the given incoming HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>The URI that the outside world used to create this request.</returns>
- internal static Uri GetPublicFacingUrl(this HttpRequestBase request) {
- Requires.NotNull(request, "request");
- return GetPublicFacingUrl(request, request.ServerVariables);
- }
-
- /// <summary>
/// Gets the query or form data from the original request (before any URL rewriting has occurred.)
/// </summary>
/// <param name="request">The request.</param>
@@ -1995,6 +2016,37 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// A thread-safe, non-crypto random number generator.
+ /// </summary>
+ private static class ThreadSafeRandom {
+ /// <summary>
+ /// The initializer of all new <see cref="Random"/> instances.
+ /// </summary>
+ private static readonly Random threadRandomInitializer = new Random();
+
+ /// <summary>
+ /// A thread-local instance of <see cref="Random"/>
+ /// </summary>
+ [ThreadStatic]
+ private static Random threadRandom;
+
+ /// <summary>
+ /// Gets a random number generator for use on the current thread only.
+ /// </summary>
+ public static Random RandomNumberGenerator {
+ get {
+ if (threadRandom == null) {
+ lock (threadRandomInitializer) {
+ threadRandom = new Random(threadRandomInitializer.Next());
+ }
+ }
+
+ return threadRandom;
+ }
+ }
+ }
+
+ /// <summary>
/// A class to convert a <see cref="Comparison&lt;T&gt;"/> into an <see cref="IComparer&lt;T&gt;"/>.
/// </summary>
/// <typeparam name="T">The type of objects being compared.</typeparam>
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
index 9ef89e9..e1e9d53 100644
--- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
@@ -43,6 +43,7 @@ namespace DotNetOpenAuth.Messaging {
internal OutgoingWebResponse() {
this.Status = HttpStatusCode.OK;
this.Headers = new WebHeaderCollection();
+ this.Cookies = new HttpCookieCollection();
}
/// <summary>
@@ -56,6 +57,7 @@ namespace DotNetOpenAuth.Messaging {
this.Status = response.StatusCode;
this.Headers = response.Headers;
+ this.Cookies = new HttpCookieCollection();
this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength);
using (Stream responseStream = response.GetResponseStream()) {
// BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here.
@@ -86,6 +88,11 @@ namespace DotNetOpenAuth.Messaging {
public bool IsResponseTruncated { get; internal set; }
/// <summary>
+ /// Gets the cookies collection to add as headers to the HTTP response.
+ /// </summary>
+ public HttpCookieCollection Cookies { get; internal set; }
+
+ /// <summary>
/// Gets or sets the body of the response as a string.
/// </summary>
public string Body {
@@ -239,6 +246,17 @@ namespace DotNetOpenAuth.Messaging {
response.StatusCode = (int)this.Status;
MessagingUtilities.ApplyHeadersToResponse(this.Headers, response);
+ foreach (HttpCookie httpCookie in this.Cookies) {
+ var cookie = new Cookie(httpCookie.Name, httpCookie.Value) {
+ Expires = httpCookie.Expires,
+ Path = httpCookie.Path,
+ HttpOnly = httpCookie.HttpOnly,
+ Secure = httpCookie.Secure,
+ Domain = httpCookie.Domain,
+ };
+ response.AppendCookie(cookie);
+ }
+
if (this.ResponseStream != null) {
response.ContentLength64 = this.ResponseStream.Length;
this.ResponseStream.CopyTo(response.OutputStream);
@@ -346,6 +364,11 @@ namespace DotNetOpenAuth.Messaging {
}
}
+ foreach (string cookieName in this.Cookies) {
+ var cookie = this.Cookies[cookieName];
+ context.Response.AppendCookie(cookie);
+ }
+
if (endRequest) {
// This approach throws an exception in order that
// no more code is executed in the calling page.
diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs
new file mode 100644
index 0000000..66b7f3c
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/IMessagePartFormattingEncoder.cs
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------
+// <copyright file="IMessagePartFormattingEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging.Reflection {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// An interface describing how various objects can be serialized and deserialized between their object and string forms.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface must include a default constructor and must be thread-safe.
+ /// </remarks>
+ public interface IMessagePartFormattingEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form.
+ /// </summary>
+ Type FormattingType { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs
index 0f140d6..a6e8da2 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/MessagePart.cs
@@ -216,6 +216,20 @@ namespace DotNetOpenAuth.Messaging.Reflection {
}
/// <summary>
+ /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form.
+ /// </summary>
+ internal Type PreferredFormattingType {
+ get {
+ var formattingEncoder = this.converter.Encoder as IMessagePartFormattingEncoder;
+ if (formattingEncoder != null) {
+ return formattingEncoder.FormattingType;
+ }
+
+ return this.MemberDeclaredType;
+ }
+ }
+
+ /// <summary>
/// Sets the member of a given message to some given value.
/// Used in deserialization.
/// </summary>
diff --git a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs
index bc12f5d..4139f52 100644
--- a/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/Reflection/ValueMapping.cs
@@ -35,7 +35,8 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// <param name="toString">The mapping function that converts some custom value to a string.</param>
/// <param name="toOriginalString">The mapping function that converts some custom value to its original (non-normalized) string. May be null if the same as the <paramref name="toString"/> function.</param>
/// <param name="toValue">The mapping function that converts a string to some custom value.</param>
- internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue) {
+ internal ValueMapping(Func<object, string> toString, Func<object, string> toOriginalString, Func<string, object> toValue)
+ : this() {
Requires.NotNull(toString, "toString");
Requires.NotNull(toValue, "toValue");
@@ -48,8 +49,11 @@ namespace DotNetOpenAuth.Messaging.Reflection {
/// Initializes a new instance of the <see cref="ValueMapping"/> struct.
/// </summary>
/// <param name="encoder">The encoder.</param>
- internal ValueMapping(IMessagePartEncoder encoder) {
+ internal ValueMapping(IMessagePartEncoder encoder)
+ : this() {
Requires.NotNull(encoder, "encoder");
+
+ this.Encoder = encoder;
var nullEncoder = encoder as IMessagePartNullEncoder;
string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null;
@@ -63,5 +67,10 @@ namespace DotNetOpenAuth.Messaging.Reflection {
this.StringToValue = str => (str != null) ? encoder.Decode(str) : null;
this.ValueToOriginalString = obj => (obj != null) ? originalStringEncode(obj) : nullString;
}
+
+ /// <summary>
+ /// Gets the encoder.
+ /// </summary>
+ internal IMessagePartEncoder Encoder { get; private set; }
}
}
diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs
index 114c191..adca925 100644
--- a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs
@@ -151,14 +151,25 @@ namespace DotNetOpenAuth.Messaging {
return new NetworkDirectWebResponse(request.RequestUri, response);
}
- if (Logger.Http.IsErrorEnabled) {
- if (response != null) {
+ if (response != null) {
+ Logger.Http.ErrorFormat(
+ "{0} returned {1} {2}: {3}",
+ response.ResponseUri,
+ (int)response.StatusCode,
+ response.StatusCode,
+ response.StatusDescription);
+
+ if (Logger.Http.IsDebugEnabled) {
using (var reader = new StreamReader(ex.Response.GetResponseStream())) {
- Logger.Http.ErrorFormat("WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd());
+ Logger.Http.DebugFormat(
+ "WebException from {0}: {1}{2}", ex.Response.ResponseUri, Environment.NewLine, reader.ReadToEnd());
}
- } else {
- Logger.Http.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status);
}
+ } else {
+ Logger.Http.ErrorFormat(
+ "{0} connecting to {0}",
+ ex.Status,
+ request.RequestUri);
}
// Be sure to close the response stream to conserve resources and avoid
diff --git a/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs
index 842176a..abce20a 100644
--- a/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs
+++ b/src/DotNetOpenAuth.Core/Messaging/TimespanSecondsEncoder.cs
@@ -12,7 +12,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Encodes and decodes the <see cref="TimeSpan"/> as an integer of total seconds.
/// </summary>
- internal class TimespanSecondsEncoder : IMessagePartEncoder {
+ internal class TimespanSecondsEncoder : IMessagePartEncoder, IMessagePartFormattingEncoder {
/// <summary>
/// Initializes a new instance of the <see cref="TimespanSecondsEncoder"/> class.
/// </summary>
@@ -20,6 +20,17 @@ namespace DotNetOpenAuth.Messaging {
// Note that this constructor is public so it can be instantiated via Activator.
}
+ #region IMessagePartFormattingEncoder members
+
+ /// <summary>
+ /// Gets the type of the encoded values produced by this encoder, as they would appear in their preferred form.
+ /// </summary>
+ public Type FormattingType {
+ get { return typeof(int); }
+ }
+
+ #endregion
+
#region IMessagePartEncoder Members
/// <summary>
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
index 34d59ee..73c563b 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/DotNetOpenAuth.OAuth2.AuthorizationServer.csproj
@@ -27,6 +27,8 @@
<DependentUpon>AuthServerStrings.resx</DependentUpon>
</Compile>
<Compile Include="OAuth2\AuthServerUtilities.cs" />
+ <Compile Include="OAuth2\AutomatedAuthorizationCheckResponse.cs" />
+ <Compile Include="OAuth2\AutomatedUserAuthorizationCheckResponse.cs" />
<Compile Include="OAuth2\ChannelElements\AggregatingClientCredentialReader.cs" />
<Compile Include="OAuth2\ChannelElements\ClientCredentialHttpBasicReader.cs" />
<Compile Include="OAuth2\ChannelElements\ClientCredentialMessagePartReader.cs" />
@@ -42,6 +44,7 @@
<Compile Include="OAuth2\IAuthorizationServerHost.cs" />
<Compile Include="OAuth2\Messages\AccessTokenAuthorizationCodeRequestAS.cs" />
<Compile Include="OAuth2\Messages\AccessTokenRefreshRequestAS.cs" />
+ <Compile Include="OAuth2\Messages\AccessTokenResult.cs" />
<Compile Include="OAuth2\Messages\EndUserAuthorizationSuccessAuthCodeResponseAS.cs" />
<Compile Include="OAuth2\Messages\IAuthorizationCodeCarryingRequest.cs" />
<Compile Include="OAuth2\Messages\IRefreshTokenCarryingRequest.cs" />
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
index 1e404e7..050a4ab 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AuthorizationServer.cs
@@ -265,6 +265,25 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Decodes a refresh token into its authorization details.
+ /// </summary>
+ /// <param name="refreshToken">The encoded refresh token as it would appear to the client.</param>
+ /// <returns>A description of the authorization represented by the refresh token.</returns>
+ /// <exception cref="ProtocolException">Thrown if the refresh token is not valid due to expiration, corruption or not being authentic.</exception>
+ /// <remarks>
+ /// This can be useful if the authorization server supports the client revoking its own access (on uninstall, for example).
+ /// Outside the scope of the OAuth 2 spec, the client may contact the authorization server host requesting that its refresh
+ /// token be revoked. The authorization server would need to decode the refresh token so it knows which authorization in
+ /// the database to delete.
+ /// </remarks>
+ public IAuthorizationDescription DecodeRefreshToken(string refreshToken) {
+ var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.CryptoKeyStore);
+ var token = new RefreshToken();
+ refreshTokenFormatter.Deserialize(token, refreshToken);
+ return token;
+ }
+
+ /// <summary>
/// Gets the redirect URL to use for a particular authorization request.
/// </summary>
/// <param name="authorizationRequest">The authorization request.</param>
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedAuthorizationCheckResponse.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedAuthorizationCheckResponse.cs
new file mode 100644
index 0000000..0179d05
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedAuthorizationCheckResponse.cs
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------
+// <copyright file="AutomatedAuthorizationCheckResponse.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.Text;
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Describes the result of an automated authorization check, such as for client credential or resource owner password grants.
+ /// </summary>
+ public class AutomatedAuthorizationCheckResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AutomatedAuthorizationCheckResponse" /> class.
+ /// </summary>
+ /// <param name="accessRequest">The access token request.</param>
+ /// <param name="approved">A value indicating whether the authorization should be approved.</param>
+ public AutomatedAuthorizationCheckResponse(IAccessTokenRequest accessRequest, bool approved) {
+ Requires.NotNull(accessRequest, "accessRequest");
+
+ this.IsApproved = approved;
+ this.ApprovedScope = new HashSet<string>(accessRequest.Scope);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the authorization should be approved.
+ /// </summary>
+ public bool IsApproved { get; private set; }
+
+ /// <summary>
+ /// Gets the scope to be granted.
+ /// </summary>
+ public HashSet<string> ApprovedScope { get; private set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedUserAuthorizationCheckResponse.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedUserAuthorizationCheckResponse.cs
new file mode 100644
index 0000000..b62807c
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/AutomatedUserAuthorizationCheckResponse.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="AutomatedUserAuthorizationCheckResponse.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.Text;
+
+ using DotNetOpenAuth.OAuth2.Messages;
+
+ /// <summary>
+ /// Describes the result of an automated authorization check for resource owner grants.
+ /// </summary>
+ public class AutomatedUserAuthorizationCheckResponse : AutomatedAuthorizationCheckResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AutomatedUserAuthorizationCheckResponse" /> class.
+ /// </summary>
+ /// <param name="accessRequest">The access token request.</param>
+ /// <param name="approved">A value indicating whether the authorization should be approved.</param>
+ /// <param name="canonicalUserName">
+ /// Canonical username of the authorizing user (resource owner), as the resource server would recognize it.
+ /// Ignored if <paramref name="approved"/> is false.
+ /// </param>
+ public AutomatedUserAuthorizationCheckResponse(IAccessTokenRequest accessRequest, bool approved, string canonicalUserName)
+ : base(accessRequest, approved) {
+ if (approved) {
+ Requires.NotNullOrEmpty(canonicalUserName, "canonicalUserName");
+ }
+
+ this.CanonicalUserName = canonicalUserName;
+ }
+
+ /// <summary>
+ /// Gets the canonical username of the authorizing user (resource owner).
+ /// </summary>
+ public string CanonicalUserName { get; private set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
index 27b71db..500b91d 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/MessageValidationBindingElement.cs
@@ -120,15 +120,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
// 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), "TryAuthorizeResourceOwnerCredentialGrant did not initialize out parameter.");
+ var authorizeResult =
+ this.AuthorizationServer.CheckAuthorizeResourceOwnerCredentialGrant(
+ resourceOwnerPasswordCarrier.RequestingUserName, resourceOwnerPasswordCarrier.Password, resourceOwnerPasswordCarrier);
+ if (authorizeResult.IsApproved) {
resourceOwnerPasswordCarrier.CredentialsValidated = true;
- resourceOwnerPasswordCarrier.UserName = canonicalUserName;
+ resourceOwnerPasswordCarrier.RequestingUserName = authorizeResult.CanonicalUserName;
+ resourceOwnerPasswordCarrier.Scope.ResetContents(authorizeResult.ApprovedScope);
} else {
Logger.OAuth.ErrorFormat(
"Resource owner password credential for user \"{0}\" rejected by authorization server host.",
- resourceOwnerPasswordCarrier.UserName);
+ resourceOwnerPasswordCarrier.RequestingUserName);
throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.InvalidGrant, AuthServerStrings.InvalidResourceOwnerPasswordCredential);
}
} catch (NotSupportedException) {
@@ -140,12 +142,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
applied = true;
} else if (clientCredentialOnly != null) {
try {
- if (!this.AuthorizationServer.TryAuthorizeClientCredentialsGrant(clientCredentialOnly)) {
+ var authorizeResult = this.AuthorizationServer.CheckAuthorizeClientCredentialsGrant(clientCredentialOnly);
+ if (!authorizeResult.IsApproved) {
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);
}
+
+ clientCredentialOnly.Scope.ResetContents(authorizeResult.ApprovedScope);
} catch (NotSupportedException) {
throw new TokenEndpointProtocolException(accessTokenRequest, Protocol.AccessTokenRequestErrorCodes.UnsupportedGrantType);
} catch (NotImplementedException) {
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs
index 494a10b..5a1dbae 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/ChannelElements/TokenCodeSerializationBindingElement.cs
@@ -103,7 +103,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
if (authCodeCarrier != null) {
var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer);
var authorizationCode = new AuthorizationCode();
- authorizationCodeFormatter.Deserialize(authorizationCode, message, authCodeCarrier.Code, Protocol.code);
+ authorizationCodeFormatter.Deserialize(authorizationCode, authCodeCarrier.Code, message, Protocol.code);
authCodeCarrier.AuthorizationDescription = authorizationCode;
}
@@ -111,7 +111,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
if (refreshTokenCarrier != null) {
var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore);
var refreshToken = new RefreshToken();
- refreshTokenFormatter.Deserialize(refreshToken, message, refreshTokenCarrier.RefreshToken, Protocol.refresh_token);
+ refreshTokenFormatter.Deserialize(refreshToken, refreshTokenCarrier.RefreshToken, message, Protocol.refresh_token);
refreshTokenCarrier.AuthorizationDescription = refreshToken;
}
diff --git a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs
index b75cb29..b9b5725 100644
--- a/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/IAuthorizationServerHost.cs
@@ -91,17 +91,11 @@ namespace DotNetOpenAuth.OAuth2 {
/// 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>
+ /// <returns>A value that describes the result of the authorization check.</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);
+ AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest);
/// <summary>
/// Determines whether an access token request given a client credential grant should be authorized
@@ -112,17 +106,15 @@ namespace DotNetOpenAuth.OAuth2 {
/// 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>
+ /// <returns>A value that describes the result of the authorization check.</returns>
/// <exception cref="NotSupportedException">
/// May be thrown if the authorization server does not support the client credential grant type.
/// </exception>
- bool TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest);
+ AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest);
}
/// <summary>
- /// Code Contract for the <see cref="IAuthorizationServerHost"/> interface.
+ /// Code Contract for the <see cref="IAuthorizationServerHost" /> interface.
/// </summary>
[ContractClassFor(typeof(IAuthorizationServerHost))]
internal abstract class IAuthorizationServerHostContract : IAuthorizationServerHost {
@@ -203,40 +195,34 @@ namespace DotNetOpenAuth.OAuth2 {
/// 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>.
+ /// A value that describes the result of the authorization check.
/// </returns>
/// <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) {
+ AutomatedUserAuthorizationCheckResponse IAuthorizationServerHost.CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest) {
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)));
+ Contract.Ensures(Contract.Result<AutomatedUserAuthorizationCheckResponse>() != null);
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
+ /// 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>
+ /// <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>.
+ /// A value that describes the result of the authorization check.
/// </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) {
+ /// <exception cref="NotSupportedException">May be thrown if the authorization server does not support the client credential grant type.</exception>
+ AutomatedAuthorizationCheckResponse IAuthorizationServerHost.CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) {
+ Contract.Requires(accessRequest != null);
+ Contract.Ensures(Contract.Result<AutomatedAuthorizationCheckResponse>() != null);
throw new NotImplementedException();
}
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenResult.cs
index 11e486b..4297165 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResult.cs
+++ b/src/DotNetOpenAuth.OAuth2.AuthorizationServer/OAuth2/Messages/AccessTokenResult.cs
@@ -14,12 +14,12 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Describes the parameters to be fed into creating a response to an access token request.
/// </summary>
- public class AccessTokenResult {
+ public class AccessTokenResult : IAccessTokenResult {
/// <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) {
+ public AccessTokenResult(AuthorizationServerAccessToken accessToken) {
Requires.NotNull(accessToken, "accessToken");
this.AllowRefreshToken = true;
this.AccessToken = accessToken;
@@ -38,6 +38,13 @@ namespace DotNetOpenAuth.OAuth2 {
/// <summary>
/// Gets the access token.
/// </summary>
- public AccessToken AccessToken { get; private set; }
+ public AuthorizationServerAccessToken AccessToken { get; private set; }
+
+ /// <summary>
+ /// Gets the access token.
+ /// </summary>
+ AccessToken IAccessTokenResult.AccessToken {
+ get { return this.AccessToken; }
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs
index 1ee5aa5..3bb6ffc 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/Configuration/OAuth2ClientSection.cs
@@ -5,6 +5,7 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Configuration {
+ using System;
using System.Configuration;
using System.Diagnostics.Contracts;
@@ -18,6 +19,11 @@ namespace DotNetOpenAuth.Configuration {
private const string SectionName = OAuth2SectionGroup.SectionName + "/client";
/// <summary>
+ /// The name of the @maxAuthorizationTime attribute.
+ /// </summary>
+ private const string MaxAuthorizationTimePropertyName = "maxAuthorizationTime";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="OAuth2ClientSection"/> class.
/// </summary>
internal OAuth2ClientSection() {
@@ -32,5 +38,24 @@ namespace DotNetOpenAuth.Configuration {
return (OAuth2ClientSection)ConfigurationManager.GetSection(SectionName) ?? new OAuth2ClientSection();
}
}
+
+ /// <summary>
+ /// Gets or sets the maximum time a user can take to complete authentication.
+ /// </summary>
+ [ConfigurationProperty(MaxAuthorizationTimePropertyName, DefaultValue = "0:15")] // 15 minutes
+ [PositiveTimeSpanValidator]
+ internal TimeSpan MaxAuthorizationTime {
+ get {
+ Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero);
+ TimeSpan result = (TimeSpan)this[MaxAuthorizationTimePropertyName];
+ Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this
+ return result;
+ }
+
+ set {
+ Requires.InRange(value > TimeSpan.Zero, "value");
+ this[MaxAuthorizationTimePropertyName] = value;
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs
index c802be6..db8b9f8 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/IOAuth2ChannelWithClient.cs
@@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
using System.Collections.Generic;
using System.Linq;
using System.Text;
+ using System.Xml;
/// <summary>
/// An interface that defines the OAuth2 client specific channel additions.
@@ -23,5 +24,10 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// Gets or sets the client credentials applicator extension to use.
/// </summary>
ClientCredentialApplicator ClientCredentialApplicator { get; set; }
+
+ /// <summary>
+ /// Gets quotas used when deserializing JSON.
+ /// </summary>
+ XmlDictionaryReaderQuotas JsonReaderQuotas { get; }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
index 8ad2ed9..b0cdb4b 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ChannelElements/OAuth2ClientChannel.cs
@@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
using System.Diagnostics.Contracts;
using System.Net;
using System.Web;
+ using System.Xml;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.Messages;
@@ -50,6 +51,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
public ClientCredentialApplicator ClientCredentialApplicator { get; set; }
/// <summary>
+ /// Gets quotas used when deserializing JSON.
+ /// </summary>
+ public XmlDictionaryReaderQuotas JsonReaderQuotas {
+ get { return this.XmlDictionaryReaderQuotas; }
+ }
+
+ /// <summary>
/// Prepares an HTTP request that carries a given message.
/// </summary>
/// <param name="request">The message to send.</param>
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
index 49d0732..e9c952d 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
@@ -16,6 +16,8 @@ namespace DotNetOpenAuth.OAuth2 {
#endif
using System.Security;
using System.Text;
+ using System.Xml;
+
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
@@ -71,6 +73,13 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
+ /// Gets quotas used when deserializing JSON.
+ /// </summary>
+ public XmlDictionaryReaderQuotas JsonReaderQuotas {
+ get { return this.OAuthChannel.JsonReaderQuotas; }
+ }
+
+ /// <summary>
/// Gets the OAuth client channel.
/// </summary>
internal IOAuth2ChannelWithClient OAuthChannel {
@@ -87,7 +96,20 @@ namespace DotNetOpenAuth.OAuth2 {
Requires.NotNull(request, "request");
Requires.NotNullOrEmpty(accessToken, "accessToken");
- OAuthUtilities.AuthorizeWithBearerToken(request, accessToken);
+ AuthorizeRequest(request.Headers, accessToken);
+ }
+
+ /// <summary>
+ /// Adds the necessary HTTP Authorization header to an HTTP request for protected resources
+ /// so that the Service Provider will allow the request through.
+ /// </summary>
+ /// <param name="requestHeaders">The headers on the request for protected resources from the service provider.</param>
+ /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param>
+ public static void AuthorizeRequest(WebHeaderCollection requestHeaders, string accessToken) {
+ Requires.NotNull(requestHeaders, "requestHeaders");
+ Requires.NotNullOrEmpty(accessToken, "accessToken");
+
+ OAuthUtilities.AuthorizeWithBearerToken(requestHeaders, accessToken);
}
/// <summary>
@@ -99,6 +121,19 @@ namespace DotNetOpenAuth.OAuth2 {
public void AuthorizeRequest(HttpWebRequest request, IAuthorizationState authorization) {
Requires.NotNull(request, "request");
Requires.NotNull(authorization, "authorization");
+
+ this.AuthorizeRequest(request.Headers, authorization);
+ }
+
+ /// <summary>
+ /// Adds the OAuth authorization token to an outgoing HTTP request, renewing a
+ /// (nearly) expired access token if necessary.
+ /// </summary>
+ /// <param name="requestHeaders">The headers on the request for protected resources from the service provider.</param>
+ /// <param name="authorization">The authorization for this request previously obtained via OAuth.</param>
+ public void AuthorizeRequest(WebHeaderCollection requestHeaders, IAuthorizationState authorization) {
+ Requires.NotNull(requestHeaders, "requestHeaders");
+ Requires.NotNull(authorization, "authorization");
Requires.True(!string.IsNullOrEmpty(authorization.AccessToken), "authorization");
ErrorUtilities.VerifyProtocol(!authorization.AccessTokenExpirationUtc.HasValue || authorization.AccessTokenExpirationUtc >= DateTime.UtcNow || authorization.RefreshToken != null, ClientStrings.AuthorizationExpired);
@@ -107,7 +142,7 @@ namespace DotNetOpenAuth.OAuth2 {
this.RefreshAuthorization(authorization);
}
- AuthorizeRequest(request, authorization.AccessToken);
+ AuthorizeRequest(requestHeaders, authorization.AccessToken);
}
#if CLR4
@@ -216,7 +251,7 @@ namespace DotNetOpenAuth.OAuth2 {
Requires.NotNull(password, "password");
var request = new AccessTokenResourceOwnerPasswordCredentialsRequest(this.AuthorizationServer.TokenEndpoint, this.AuthorizationServer.Version) {
- UserName = userName,
+ RequestingUserName = userName,
Password = password,
};
@@ -372,6 +407,7 @@ namespace DotNetOpenAuth.OAuth2 {
var failure = response as AccessTokenFailedResponse;
ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany);
if (success != null) {
+ authorizationState.Scope.Clear(); // clear the scope we requested so that the response will repopulate it.
UpdateAuthorizationWithResponse(authorizationState, success);
} else { // failure
Logger.OAuth.Info("Credentials rejected by the Authorization Server.");
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
index 939d1df..879e4e3 100644
--- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
+++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs
@@ -8,10 +8,14 @@ namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
+ using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
+ using System.Web.Security;
+
+ using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.Messages;
@@ -20,6 +24,11 @@ namespace DotNetOpenAuth.OAuth2 {
/// </summary>
public class WebServerClient : ClientBase {
/// <summary>
+ /// The cookie name for XSRF mitigation during authorization code grant flows.
+ /// </summary>
+ private const string XsrfCookieName = "DotNetOpenAuth.WebServerClient.XSRF-Session";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="WebServerClient"/> class.
/// </summary>
/// <param name="authorizationServer">The authorization server.</param>
@@ -100,16 +109,25 @@ namespace DotNetOpenAuth.OAuth2 {
// Mitigate XSRF attacks by including a state value that would be unpredictable between users, but
// verifiable for the same user/session.
// If the host is implementing the authorization tracker though, they're handling this protection themselves.
+ HttpCookie cookie = null;
if (this.AuthorizationTracker == null) {
var context = this.Channel.GetHttpContext();
- if (context.Session != null) {
- request.ClientState = context.Session.SessionID;
- } else {
- Logger.OAuth.WarnFormat("No request context discovered, so no client state parameter could be set to mitigate XSRF attacks.");
- }
+
+ string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16);
+ cookie = new HttpCookie(XsrfCookieName, xsrfKey) {
+ HttpOnly = true,
+ Secure = FormsAuthentication.RequireSSL,
+ ////Expires = DateTime.Now.Add(OAuth2ClientSection.Configuration.MaxAuthorizationTime), // we prefer session cookies to persistent ones
+ };
+ request.ClientState = xsrfKey;
+ }
+
+ var response = this.Channel.PrepareResponse(request);
+ if (cookie != null) {
+ response.Cookies.Add(cookie);
}
- return this.Channel.PrepareResponse(request);
+ return response;
}
/// <summary>
@@ -134,12 +152,9 @@ namespace DotNetOpenAuth.OAuth2 {
ErrorUtilities.VerifyProtocol(authorizationState != null, ClientStrings.AuthorizationResponseUnexpectedMismatch);
} else {
var context = this.Channel.GetHttpContext();
- if (context.Session != null) {
- ErrorUtilities.VerifyProtocol(string.Equals(response.ClientState, context.Session.SessionID, StringComparison.Ordinal), ClientStrings.AuthorizationResponseUnexpectedMismatch);
- } else {
- Logger.OAuth.WarnFormat("No request context discovered, so no client state parameter could be checked to mitigate XSRF attacks.");
- }
+ HttpCookie cookie = request.Cookies[XsrfCookieName];
+ ErrorUtilities.VerifyProtocol(cookie != null && string.Equals(response.ClientState, cookie.Value, StringComparison.Ordinal), ClientStrings.AuthorizationResponseUnexpectedMismatch);
authorizationState = new AuthorizationState { Callback = callback };
}
var success = response as EndUserAuthorizationSuccessAuthCodeResponse;
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj
index 2a08dbf..c3ed569 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/DotNetOpenAuth.OAuth2.ClientAuthorization.csproj
@@ -34,7 +34,7 @@
<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\IAccessTokenResult.cs" />
<Compile Include="OAuth2\Messages\AccessTokenSuccessResponse.cs" />
<Compile Include="OAuth2\Messages\AuthenticatedClientRequestBase.cs" />
<Compile Include="OAuth2\Messages\EndUserAuthorizationFailedResponse.cs" />
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs
index e6bbc34..e405a85 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenRequestBase.cs
@@ -45,7 +45,24 @@ 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; }
+ IAccessTokenResult IAccessTokenRequestInternal.AccessTokenResult { get; set; }
+
+ /// <summary>
+ /// Gets the username of the authorizing user, when applicable.
+ /// </summary>
+ /// <value>
+ /// A non-empty string; or <c>null</c> when no user has authorized this access token.
+ /// </value>
+ public virtual string UserName {
+ get {
+ IAccessTokenRequestInternal request = this;
+ if (request.AccessTokenResult != null && request.AccessTokenResult.AccessToken != null) {
+ return request.AccessTokenResult.AccessToken.User;
+ }
+
+ return null;
+ }
+ }
/// <summary>
/// Gets the type of the grant.
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs
index a5d958a..a2cbe90 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/AccessTokenResourceOwnerPasswordCredentialsRequest.cs
@@ -51,7 +51,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// Gets the name on the account whose data on the resource server is accessible using this authorization.
/// </summary>
string IAuthorizationDescription.User {
- get { return this.UserName; }
+ get { return this.RequestingUserName; }
}
/// <summary>
@@ -64,6 +64,16 @@ namespace DotNetOpenAuth.OAuth2.Messages {
#endregion
/// <summary>
+ /// Gets the username of the authorizing user, when applicable.
+ /// </summary>
+ /// <value>
+ /// A non-empty string; or <c>null</c> when no user has authorized this access token.
+ /// </value>
+ public override string UserName {
+ get { return base.UserName ?? this.RequestingUserName; }
+ }
+
+ /// <summary>
/// Gets the type of the grant.
/// </summary>
/// <value>The type of the grant.</value>
@@ -76,7 +86,7 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// </summary>
/// <value>The username on the user's account.</value>
[MessagePart(Protocol.username, IsRequired = true)]
- internal string UserName { get; set; }
+ internal string RequestingUserName { get; set; }
/// <summary>
/// Gets or sets the user's password.
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs
index 4b662cd..6f7ba7d 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/EndUserAuthorizationImplicitRequest.cs
@@ -45,7 +45,24 @@ 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; }
+ IAccessTokenResult IAccessTokenRequestInternal.AccessTokenResult { get; set; }
+
+ /// <summary>
+ /// Gets the username of the authorizing user, when applicable.
+ /// </summary>
+ /// <value>
+ /// A non-empty string; or <c>null</c> when no user has authorized this access token.
+ /// </value>
+ string IAccessTokenRequest.UserName {
+ get {
+ IAccessTokenRequestInternal request = this;
+ if (request.AccessTokenResult != null && request.AccessTokenResult.AccessToken != null) {
+ return request.AccessTokenResult.AccessToken.User;
+ }
+
+ return null;
+ }
+ }
/// <summary>
/// Gets a value indicating whether the client requesting the access token has authenticated itself.
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs
index 65378f9..81acb77 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequest.cs
@@ -30,6 +30,12 @@ namespace DotNetOpenAuth.OAuth2.Messages {
string ClientIdentifier { get; }
/// <summary>
+ /// Gets the username of the authorizing user, when applicable.
+ /// </summary>
+ /// <value>A non-empty string; or <c>null</c> when no user has authorized this access token.</value>
+ string UserName { get; }
+
+ /// <summary>
/// Gets the scope of operations the client is allowed to invoke.
/// </summary>
HashSet<string> Scope { get; }
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs
index 44af074..b79d566 100644
--- a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenRequestInternal.cs
@@ -17,6 +17,6 @@ namespace DotNetOpenAuth.OAuth2.Messages {
/// <summary>
/// Gets or sets the result of calling the authorization server host's access token creation method.
/// </summary>
- AccessTokenResult AccessTokenResult { get; set; }
+ IAccessTokenResult AccessTokenResult { get; set; }
}
}
diff --git a/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenResult.cs b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenResult.cs
new file mode 100644
index 0000000..1719984
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2.ClientAuthorization/OAuth2/Messages/IAccessTokenResult.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="IAccessTokenResult.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 interface IAccessTokenResult {
+ /// <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>
+ bool AllowRefreshToken { get; set; }
+
+ /// <summary>
+ /// Gets the access token.
+ /// </summary>
+ AccessToken AccessToken { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
index 33b99f9..62436ec 100644
--- a/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
+++ b/src/DotNetOpenAuth.OAuth2.ResourceServer/OAuth2/StandardAccessTokenAnalyzer.cs
@@ -53,7 +53,7 @@ namespace DotNetOpenAuth.OAuth2 {
var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey);
var token = new AccessToken();
try {
- accessTokenFormatter.Deserialize(token, message, accessToken, Protocol.access_token);
+ accessTokenFormatter.Deserialize(token, accessToken, message, Protocol.access_token);
} catch (IOException ex) {
throw new ProtocolException(ResourceServerStrings.InvalidAccessToken, ex);
}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
index 5a4a0d3..f28518f 100644
--- a/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/OAuthUtilities.cs
@@ -136,7 +136,20 @@ namespace DotNetOpenAuth.OAuth2 {
Requires.NotNullOrEmpty(accessToken, "accessToken");
ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), OAuthStrings.AccessTokenInvalidForHttpAuthorizationHeader);
- request.Headers[HttpRequestHeader.Authorization] = string.Format(
+ AuthorizeWithBearerToken(request.Headers, accessToken);
+ }
+
+ /// <summary>
+ /// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header.
+ /// </summary>
+ /// <param name="requestHeaders">The headers on the request for protected resources from the service provider.</param>
+ /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param>
+ internal static void AuthorizeWithBearerToken(WebHeaderCollection requestHeaders, string accessToken) {
+ Requires.NotNull(requestHeaders, "requestHeaders");
+ Requires.NotNullOrEmpty(accessToken, "accessToken");
+ ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), OAuthStrings.AccessTokenInvalidForHttpAuthorizationHeader);
+
+ requestHeaders[HttpRequestHeader.Authorization] = string.Format(
CultureInfo.InvariantCulture,
Protocol.BearerHttpAuthorizationHeaderFormat,
accessToken);
diff --git a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs
index 5c39c5e..897b4f8 100644
--- a/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs
+++ b/src/DotNetOpenAuth.OpenId.Provider/OpenId/Provider/ProviderAssociationHandleEncoder.cs
@@ -70,7 +70,7 @@ namespace DotNetOpenAuth.OpenId.Provider {
var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket);
AssociationDataBag bag = new AssociationDataBag();
try {
- formatter.Deserialize(bag, containingMessage, handle, Protocol.Default.openid.assoc_handle);
+ formatter.Deserialize(bag, handle, containingMessage, 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.UI/XrdsPublisher.cs b/src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs
index 5aa3064..87e3a8b 100644
--- a/src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs
+++ b/src/DotNetOpenAuth.OpenId.UI/XrdsPublisher.cs
@@ -198,6 +198,7 @@ namespace DotNetOpenAuth {
// client since it isn't interested anyway.
// We do NOT simply send a 301 redirect here because that would
// alter the Claimed Identifier.
+ Logger.Yadis.InfoFormat("Transferring request from {0} to {1} to respond to XRDS discovery request.", this.Page.Request.Url.AbsoluteUri, this.XrdsUrl);
this.Page.Server.Transfer(this.XrdsUrl);
}
}
diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs
index 8620b93..cf0f9ca 100644
--- a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs
+++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs
@@ -220,13 +220,17 @@ namespace DotNetOpenAuth.Test.Messaging {
Assert.AreEqual("POST", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PostRequest));
Assert.AreEqual("HEAD", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.HeadRequest));
Assert.AreEqual("DELETE", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.DeleteRequest));
- Assert.AreEqual("PUT", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PutRequest));
+ Assert.AreEqual("PUT", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PutRequest));
+ Assert.AreEqual("PATCH", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PatchRequest));
+ Assert.AreEqual("OPTIONS", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.OptionsRequest));
Assert.AreEqual("GET", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
Assert.AreEqual("POST", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
Assert.AreEqual("HEAD", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.HeadRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
Assert.AreEqual("DELETE", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.DeleteRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
- Assert.AreEqual("PUT", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PutRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
+ Assert.AreEqual("PUT", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PutRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
+ Assert.AreEqual("PATCH", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.PatchRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
+ Assert.AreEqual("OPTIONS", MessagingUtilities.GetHttpVerb(HttpDeliveryMethods.OptionsRequest | HttpDeliveryMethods.AuthorizationHeaderRequest));
}
/// <summary>
@@ -246,7 +250,9 @@ namespace DotNetOpenAuth.Test.Messaging {
Assert.AreEqual(HttpDeliveryMethods.PostRequest, MessagingUtilities.GetHttpDeliveryMethod("POST"));
Assert.AreEqual(HttpDeliveryMethods.HeadRequest, MessagingUtilities.GetHttpDeliveryMethod("HEAD"));
Assert.AreEqual(HttpDeliveryMethods.PutRequest, MessagingUtilities.GetHttpDeliveryMethod("PUT"));
- Assert.AreEqual(HttpDeliveryMethods.DeleteRequest, MessagingUtilities.GetHttpDeliveryMethod("DELETE"));
+ Assert.AreEqual(HttpDeliveryMethods.DeleteRequest, MessagingUtilities.GetHttpDeliveryMethod("DELETE"));
+ Assert.AreEqual(HttpDeliveryMethods.PatchRequest, MessagingUtilities.GetHttpDeliveryMethod("PATCH"));
+ Assert.AreEqual(HttpDeliveryMethods.OptionsRequest, MessagingUtilities.GetHttpDeliveryMethod("OPTIONS"));
}
/// <summary>
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
index 2e09943..50eff97 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs
@@ -88,6 +88,11 @@ namespace DotNetOpenAuth.Test.Mocks {
private Action<IProtocolMessage> outgoingMessageFilter;
/// <summary>
+ /// The simulated clients cookies.
+ /// </summary>
+ private HttpCookieCollection cookies = new HttpCookieCollection();
+
+ /// <summary>
/// Initializes a new instance of the <see cref="CoordinatingChannel"/> class.
/// </summary>
/// <param name="wrappedChannel">The wrapped channel. Must not be null.</param>
@@ -158,15 +163,23 @@ namespace DotNetOpenAuth.Test.Mocks {
this.incomingMessageSignal.Set();
}
+ internal void SaveCookies(HttpCookieCollection cookies) {
+ Requires.NotNull(cookies, "cookies");
+ foreach (string cookieName in cookies) {
+ var cookie = cookies[cookieName];
+ this.cookies.Set(cookie);
+ }
+ }
+
protected internal override HttpRequestBase GetRequestFromContext() {
MessageReceivingEndpoint recipient;
WebHeaderCollection headers;
var messageData = this.AwaitIncomingMessage(out recipient, out headers);
CoordinatingHttpRequestInfo result;
if (messageData != null) {
- result = new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient);
+ result = new CoordinatingHttpRequestInfo(this, this.MessageFactory, messageData, recipient, this.cookies);
} else {
- result = new CoordinatingHttpRequestInfo(recipient);
+ result = new CoordinatingHttpRequestInfo(recipient, this.cookies);
}
if (headers != null) {
@@ -207,7 +220,7 @@ namespace DotNetOpenAuth.Test.Mocks {
protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
this.ProcessMessageFilter(response, true);
- return new CoordinatingOutgoingWebResponse(response, this.RemoteChannel);
+ return new CoordinatingOutgoingWebResponse(response, this.RemoteChannel, this);
}
protected override OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) {
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
index a1f5cf5..2713765 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs
@@ -6,10 +6,12 @@
namespace DotNetOpenAuth.Test.Mocks {
using System;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.Net;
-using DotNetOpenAuth.Messaging;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Net;
+ using System.Web;
+
+ using DotNetOpenAuth.Messaging;
internal class CoordinatingHttpRequestInfo : HttpRequestInfo {
private readonly Channel channel;
@@ -30,12 +32,14 @@ using DotNetOpenAuth.Messaging;
/// <param name="messageFactory">The message factory.</param>
/// <param name="messageData">The message data.</param>
/// <param name="recipient">The recipient.</param>
+ /// <param name="cookies">Cookies included in the incoming request.</param>
internal CoordinatingHttpRequestInfo(
Channel channel,
IMessageFactory messageFactory,
IDictionary<string, string> messageData,
- MessageReceivingEndpoint recipient)
- : this(recipient) {
+ MessageReceivingEndpoint recipient,
+ HttpCookieCollection cookies)
+ : this(recipient, cookies) {
Contract.Requires(channel != null);
Contract.Requires(messageFactory != null);
Contract.Requires(messageData != null);
@@ -49,8 +53,9 @@ using DotNetOpenAuth.Messaging;
/// that will not generate any message.
/// </summary>
/// <param name="recipient">The recipient.</param>
- internal CoordinatingHttpRequestInfo(MessageReceivingEndpoint recipient)
- : base(GetHttpVerb(recipient), recipient != null ? recipient.Location : new Uri("http://host/path")) {
+ /// <param name="cookies">Cookies included in the incoming request.</param>
+ internal CoordinatingHttpRequestInfo(MessageReceivingEndpoint recipient, HttpCookieCollection cookies)
+ : base(GetHttpVerb(recipient), recipient != null ? recipient.Location : new Uri("http://host/path"), cookies: cookies) {
this.recipient = recipient;
}
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs
index 52f381d..96091ac 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuth2ClientChannel.cs
@@ -29,5 +29,9 @@ namespace DotNetOpenAuth.Test.Mocks {
get { return this.wrappedChannel.ClientCredentialApplicator; }
set { this.wrappedChannel.ClientCredentialApplicator = value; }
}
+
+ public System.Xml.XmlDictionaryReaderQuotas JsonReaderQuotas {
+ get { return this.XmlDictionaryReaderQuotas; }
+ }
}
} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs
index 8d2c1e7..90dbd7d 100644
--- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs
+++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs
@@ -16,16 +16,21 @@ namespace DotNetOpenAuth.Test.Mocks {
internal class CoordinatingOutgoingWebResponse : OutgoingWebResponse {
private CoordinatingChannel receivingChannel;
+ private CoordinatingChannel sendingChannel;
+
/// <summary>
/// Initializes a new instance of the <see cref="CoordinatingOutgoingWebResponse"/> class.
/// </summary>
/// <param name="message">The direct response message to send to the remote channel. This message will be cloned.</param>
/// <param name="receivingChannel">The receiving channel.</param>
- internal CoordinatingOutgoingWebResponse(IProtocolMessage message, CoordinatingChannel receivingChannel) {
+ /// <param name="sendingChannel">The sending channel.</param>
+ internal CoordinatingOutgoingWebResponse(IProtocolMessage message, CoordinatingChannel receivingChannel, CoordinatingChannel sendingChannel) {
Requires.NotNull(message, "message");
Requires.NotNull(receivingChannel, "receivingChannel");
+ Requires.NotNull(sendingChannel, "sendingChannel");
this.receivingChannel = receivingChannel;
+ this.sendingChannel = sendingChannel;
this.OriginalMessage = message;
}
@@ -35,6 +40,7 @@ namespace DotNetOpenAuth.Test.Mocks {
}
public override void Respond() {
+ this.sendingChannel.SaveCookies(this.Cookies);
this.receivingChannel.PostMessage(this.OriginalMessage);
}
}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs
index 487ce56..49260eb 100644
--- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs
@@ -5,6 +5,9 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Test.OAuth.ChannelElements {
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using DotNetOpenAuth.Test.Mocks;
@@ -20,5 +23,25 @@ namespace DotNetOpenAuth.Test.OAuth.ChannelElements {
hmac.Channel = new TestChannel(this.MessageDescriptions);
Assert.AreEqual("kR0LhH8UqylaLfR/esXVVlP4sQI=", hmac.GetSignatureTestHook(message));
}
+
+ [Test]
+ public void LinkedInInteropTest() {
+ var endpoint = new MessageReceivingEndpoint("https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary)", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest);
+ var message = new AccessProtectedResourceRequest(endpoint, Protocol.V10.Version);
+ message.ConsumerKey = "ub78frzrn0yf";
+ message.AccessToken = "852863fd-05da-4d80-a93d-50f64f966de4";
+ ((ITamperResistantOAuthMessage)message).ConsumerSecret = "ExJXsYl7Or8OfK98";
+ ((ITamperResistantOAuthMessage)message).TokenSecret = "b197333b-470a-43b3-bcd7-49d6d2229c4c";
+ var signedMessage = (ITamperResistantOAuthMessage)message;
+ signedMessage.HttpMethod = "GET";
+ signedMessage.SignatureMethod = "HMAC-SHA1";
+ MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(message);
+ dictionary["oauth_timestamp"] = "1353545248";
+ dictionary["oauth_nonce"] = "ugEB4bst";
+
+ var hmac = new HmacSha1SigningBindingElement();
+ hmac.Channel = new TestChannel(this.MessageDescriptions);
+ Assert.That(hmac.GetSignatureTestHook(message), Is.EqualTo("l09yeD9cr4+h1eoUF4WBoGEHrlk="));
+ }
}
}
diff --git a/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs b/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs
index 3791e28..e8f7172 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/AuthorizationServerTests.cs
@@ -9,8 +9,11 @@ namespace DotNetOpenAuth.Test.OAuth2 {
using System.Collections.Generic;
using System.Linq;
using System.Text;
+ using System.Threading.Tasks;
using DotNetOpenAuth.OAuth2;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
using DotNetOpenAuth.OAuth2.Messages;
+ using Moq;
using NUnit.Framework;
/// <summary>
@@ -28,8 +31,7 @@ namespace DotNetOpenAuth.Test.OAuth2 {
AuthorizationServerMock,
new UserAgentClient(AuthorizationServerDescription),
client => {
- var request = new AccessTokenAuthorizationCodeRequestC(AuthorizationServerDescription)
- { ClientIdentifier = ClientId, ClientSecret = ClientSecret, AuthorizationCode = "foo" };
+ var request = new AccessTokenAuthorizationCodeRequestC(AuthorizationServerDescription) { ClientIdentifier = ClientId, ClientSecret = ClientSecret, AuthorizationCode = "foo" };
var response = client.Channel.Request<AccessTokenFailedResponse>(request);
Assert.That(response.Error, Is.Not.Null.And.Not.Empty);
@@ -40,5 +42,182 @@ namespace DotNetOpenAuth.Test.OAuth2 {
});
coordinator.Run();
}
+
+ [Test]
+ public void DecodeRefreshToken() {
+ var refreshTokenSource = new TaskCompletionSource<string>();
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ AuthorizationServerMock,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ try {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ client.PrepareRequestUserAuthorization(authState).Respond();
+ var result = client.ProcessUserAuthorization();
+ Assert.That(result.AccessToken, Is.Not.Null.And.Not.Empty);
+ Assert.That(result.RefreshToken, Is.Not.Null.And.Not.Empty);
+ refreshTokenSource.SetResult(result.RefreshToken);
+ } catch {
+ refreshTokenSource.TrySetCanceled();
+ }
+ },
+ server => {
+ var request = server.ReadAuthorizationRequest();
+ Assert.That(request, Is.Not.Null);
+ server.ApproveAuthorizationRequest(request, ResourceOwnerUsername);
+ server.HandleTokenRequest().Respond();
+ var authorization = server.DecodeRefreshToken(refreshTokenSource.Task.Result);
+ Assert.That(authorization, Is.Not.Null);
+ Assert.That(authorization.User, Is.EqualTo(ResourceOwnerUsername));
+ });
+ coordinator.Run();
+ }
+
+ [Test]
+ public void ResourceOwnerScopeOverride() {
+ var clientRequestedScopes = new[] { "scope1", "scope2" };
+ var serverOverriddenScopes = new[] { "scope1", "differentScope" };
+ var authServerMock = CreateAuthorizationServerMock();
+ authServerMock
+ .Setup(a => a.CheckAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>()))
+ .Returns<string, string, IAccessTokenRequest>((un, pw, req) => {
+ var response = new AutomatedUserAuthorizationCheckResponse(req, true, ResourceOwnerUsername);
+ response.ApprovedScope.Clear();
+ response.ApprovedScope.UnionWith(serverOverriddenScopes);
+ return response;
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServerMock.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ var result = client.ExchangeUserCredentialForToken(ResourceOwnerUsername, ResourceOwnerPassword, clientRequestedScopes);
+ Assert.That(result.Scope, Is.EquivalentTo(serverOverriddenScopes));
+ },
+ server => {
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
+
+ [Test]
+ public void CreateAccessTokenSeesAuthorizingUserResourceOwnerGrant() {
+ var authServerMock = CreateAuthorizationServerMock();
+ authServerMock
+ .Setup(a => a.CheckAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>()))
+ .Returns<string, string, IAccessTokenRequest>((un, pw, req) => {
+ var response = new AutomatedUserAuthorizationCheckResponse(req, true, ResourceOwnerUsername);
+ Assert.That(req.UserName, Is.EqualTo(ResourceOwnerUsername));
+ return response;
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServerMock.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ var result = client.ExchangeUserCredentialForToken(ResourceOwnerUsername, ResourceOwnerPassword, TestScopes);
+ Assert.That(result.AccessToken, Is.Not.Null);
+ },
+ server => {
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
+
+ [Test]
+ public void CreateAccessTokenSeesAuthorizingUserClientCredentialGrant() {
+ var authServerMock = CreateAuthorizationServerMock();
+ authServerMock
+ .Setup(a => a.CheckAuthorizeClientCredentialsGrant(It.IsAny<IAccessTokenRequest>()))
+ .Returns<IAccessTokenRequest>(req => {
+ Assert.That(req.UserName, Is.Null);
+ return new AutomatedAuthorizationCheckResponse(req, true);
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServerMock.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ var result = client.GetClientAccessToken(TestScopes);
+ Assert.That(result.AccessToken, Is.Not.Null);
+ },
+ server => {
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
+
+ [Test]
+ public void CreateAccessTokenSeesAuthorizingUserAuthorizationCodeGrant() {
+ var authServerMock = CreateAuthorizationServerMock();
+ authServerMock
+ .Setup(a => a.IsAuthorizationValid(It.IsAny<IAuthorizationDescription>()))
+ .Returns<IAuthorizationDescription>(req => {
+ Assert.That(req.User, Is.EqualTo(ResourceOwnerUsername));
+ return true;
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServerMock.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ client.PrepareRequestUserAuthorization(authState).Respond();
+ var result = client.ProcessUserAuthorization();
+ Assert.That(result.AccessToken, Is.Not.Null.And.Not.Empty);
+ Assert.That(result.RefreshToken, Is.Not.Null.And.Not.Empty);
+ },
+ server => {
+ var request = server.ReadAuthorizationRequest();
+ Assert.That(request, Is.Not.Null);
+ server.ApproveAuthorizationRequest(request, ResourceOwnerUsername);
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
+
+ [Test]
+ public void ClientCredentialScopeOverride() {
+ var clientRequestedScopes = new[] { "scope1", "scope2" };
+ var serverOverriddenScopes = new[] { "scope1", "differentScope" };
+ var authServerMock = CreateAuthorizationServerMock();
+ authServerMock
+ .Setup(a => a.CheckAuthorizeClientCredentialsGrant(It.IsAny<IAccessTokenRequest>()))
+ .Returns<IAccessTokenRequest>(req => {
+ var response = new AutomatedAuthorizationCheckResponse(req, true);
+ response.ApprovedScope.Clear();
+ response.ApprovedScope.UnionWith(serverOverriddenScopes);
+ return response;
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServerMock.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = new AuthorizationState(TestScopes) {
+ Callback = ClientCallback,
+ };
+ var result = client.GetClientAccessToken(clientRequestedScopes);
+ Assert.That(result.Scope, Is.EquivalentTo(serverOverriddenScopes));
+ },
+ server => {
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
}
}
diff --git a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs
index b9e32fe..395b18c 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/OAuth2TestBase.cs
@@ -56,8 +56,9 @@ namespace DotNetOpenAuth.Test.OAuth2 {
d =>
d.ClientIdentifier == ClientId && d.User == ResourceOwnerUsername &&
MessagingUtilities.AreEquivalent(d.Scope, TestScopes)))).Returns(true);
- string canonicalUserName = ResourceOwnerUsername;
- authHostMock.Setup(m => m.TryAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>(), out canonicalUserName)).Returns(true);
+ authHostMock
+ .Setup(m => m.CheckAuthorizeResourceOwnerCredentialGrant(ResourceOwnerUsername, ResourceOwnerPassword, It.IsAny<IAccessTokenRequest>()))
+ .Returns<string, string, IAccessTokenRequest>((p1, p2, p3) => new AutomatedUserAuthorizationCheckResponse(p3, true, ResourceOwnerUsername));
authHostMock.Setup(m => m.CreateAccessToken(It.IsAny<IAccessTokenRequest>())).Returns(new AccessTokenResult(new AuthorizationServerAccessToken() { AccessTokenSigningKey = AsymmetricKey }));
return authHostMock;
}
diff --git a/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs b/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs
index a4d09de..80a8392 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/ResourceServerTests.cs
@@ -87,8 +87,8 @@ namespace DotNetOpenAuth.Test.OAuth2 {
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);
+ a => a.CheckAuthorizeClientCredentialsGrant(It.Is<IAccessTokenRequest>(d => d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes))))
+ .Returns<IAccessTokenRequest>(req => new AutomatedAuthorizationCheckResponse(req, true));
var coordinator = new OAuth2Coordinator<WebServerClient>(
AuthorizationServerDescription,
authServer.Object,
diff --git a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
index 9a9c078..8b8b29c 100644
--- a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
+++ b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
@@ -84,8 +84,8 @@ namespace DotNetOpenAuth.Test.OAuth2 {
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);
+ a => a.CheckAuthorizeClientCredentialsGrant(It.Is<IAccessTokenRequest>(d => d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes))))
+ .Returns<IAccessTokenRequest>(req => new AutomatedAuthorizationCheckResponse(req, true));
var coordinator = new OAuth2Coordinator<WebServerClient>(
AuthorizationServerDescription,
authServer.Object,
@@ -102,6 +102,34 @@ namespace DotNetOpenAuth.Test.OAuth2 {
}
[Test]
+ public void GetClientAccessTokenReturnsApprovedScope() {
+ string[] approvedScopes = new[] { "Scope2", "Scope3" };
+ var authServer = CreateAuthorizationServerMock();
+ 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.CheckAuthorizeClientCredentialsGrant(It.Is<IAccessTokenRequest>(d => d.ClientIdentifier == ClientId && MessagingUtilities.AreEquivalent(d.Scope, TestScopes))))
+ .Returns<IAccessTokenRequest>(req => {
+ var response = new AutomatedAuthorizationCheckResponse(req, true);
+ response.ApprovedScope.ResetContents(approvedScopes);
+ return response;
+ });
+ var coordinator = new OAuth2Coordinator<WebServerClient>(
+ AuthorizationServerDescription,
+ authServer.Object,
+ new WebServerClient(AuthorizationServerDescription),
+ client => {
+ var authState = client.GetClientAccessToken(TestScopes);
+ Assert.That(authState.Scope, Is.EquivalentTo(approvedScopes));
+ },
+ server => {
+ server.HandleTokenRequest().Respond();
+ });
+ coordinator.Run();
+ }
+
+ [Test]
public void CreateAuthorizingHandlerBearer() {
var client = new WebServerClient(AuthorizationServerDescription);
string bearerToken = "mytoken";
diff --git a/src/version.txt b/src/version.txt
index 53b95c0..95a7f86 100644
--- a/src/version.txt
+++ b/src/version.txt
@@ -1,3 +1,2 @@
-4.1.5
+4.2.2
-0.25.0-draft5