summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs83
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs40
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs97
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/Messages/UnauthorizedResponse.cs21
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs13
5 files changed, 164 insertions, 90 deletions
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index 40a0791..44fffe5 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -59,6 +59,21 @@ namespace DotNetOpenAuth.Messaging {
internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ";
/// <summary>
+ /// A character array containing just the = character.
+ /// </summary>
+ private static readonly char[] EqualsArray = new char[] { '=' };
+
+ /// <summary>
+ /// A character array containing just the , character.
+ /// </summary>
+ private static readonly char[] CommaArray = new char[] { ',' };
+
+ /// <summary>
+ /// A character array containing just the " character.
+ /// </summary>
+ private static readonly char[] QuoteArray = new char[] { '"' };
+
+ /// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
@@ -270,6 +285,61 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Assembles the content of the HTTP Authorization or WWW-Authenticate header.
+ /// </summary>
+ /// <param name="scheme">The scheme.</param>
+ /// <param name="fields">The fields to include.</param>
+ /// <returns>A value prepared for an HTTP header.</returns>
+ internal static string AssembleAuthorizationHeader(string scheme, IEnumerable<KeyValuePair<string, string>> fields) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(scheme));
+ Contract.Requires<ArgumentNullException>(fields != null, "fields");
+
+ var authorization = new StringBuilder();
+ authorization.Append(scheme);
+ authorization.Append(" ");
+ foreach (var pair in fields) {
+ string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key);
+ string value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value);
+ authorization.Append(key);
+ authorization.Append("=\"");
+ authorization.Append(value);
+ authorization.Append("\",");
+ }
+ authorization.Length--; // remove trailing comma
+ return authorization.ToString();
+ }
+
+ /// <summary>
+ /// Parses the authorization header.
+ /// </summary>
+ /// <param name="scheme">The scheme. Must not be null or empty.</param>
+ /// <param name="authorizationHeader">The authorization header. May be null or empty.</param>
+ /// <returns>A sequence of key=value pairs discovered in the header. Never null, but may be empty.</returns>
+ internal static IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string scheme, string authorizationHeader) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(scheme));
+ Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, string>>>() != null);
+
+ string prefix = scheme + " ";
+ if (authorizationHeader != null) {
+ // The authorization header may have multiple sections. Look for the appropriate one.
+ string[] authorizationSections = authorizationHeader.Split(';'); // TODO: is this the right delimiter?
+ foreach (string authorization in authorizationSections) {
+ string trimmedAuth = authorization.Trim();
+ if (trimmedAuth.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) {
+ string data = trimmedAuth.Substring(prefix.Length);
+ return from element in data.Split(CommaArray)
+ let parts = element.Split(EqualsArray, 2)
+ let key = Uri.UnescapeDataString(parts[0])
+ let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray))
+ select new KeyValuePair<string, string>(key, value);
+ }
+ }
+ }
+
+ return Enumerable.Empty<KeyValuePair<string, string>>();
+ }
+
+ /// <summary>
/// Gets a buffer of random data (not cryptographically strong).
/// </summary>
/// <param name="length">The length of the sequence to generate.</param>
@@ -767,6 +837,19 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Collects a sequence of key=value pairs into a dictionary.
+ /// </summary>
+ /// <typeparam name="TKey">The type of the key.</typeparam>
+ /// <typeparam name="TValue">The type of the value.</typeparam>
+ /// <param name="sequence">The sequence.</param>
+ /// <returns>A dictionary.</returns>
+ internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> sequence)
+ {
+ Contract.Requires<ArgumentNullException>(sequence != null, "sequence");
+ return sequence.ToDictionary(pair => pair.Key, pair => pair.Value);
+ }
+
+ /// <summary>
/// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
/// </summary>
/// <param name="nvc">The NameValueCollection to convert. May be null.</param>
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index dc59b56..fe1f89c 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -116,34 +116,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// <param name="request">The HTTP request to search.</param>
/// <returns>The deserialized message, if one is found. Null otherwise.</returns>
protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) {
- var fields = new Dictionary<string, string>();
-
// First search the Authorization header.
string authorization = request.Headers[HttpRequestHeader.Authorization];
- if (authorization != null) {
- string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
- string oauthPrefix = Protocol.AuthorizationHeaderScheme + " ";
-
- // The Authorization header may have multiple uses, and OAuth may be just one of them.
- // Go through each one looking for an OAuth one.
- foreach (string auth in authorizationSections) {
- string trimmedAuth = auth.Trim();
- if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
- // We found an Authorization: OAuth header.
- // Parse it according to the rules in section 5.4.1 of the V1.0 spec.
- foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
- string[] keyValueStringPair = stringPair.Trim().Split('=');
- string key = Uri.UnescapeDataString(keyValueStringPair[0]);
- string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
- fields.Add(key, value);
- }
- }
- }
- }
+ var fields = MessagingUtilities.ParseAuthorizationHeader(Protocol.AuthorizationHeaderScheme, authorization).ToDictionary();
// Scrape the entity
if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) {
- ContentType contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]);
+ var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]);
if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) {
foreach (string key in request.Form) {
if (key != null) {
@@ -344,20 +323,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri);
httpRequest.Method = GetHttpMethod(requestMessage);
- StringBuilder authorization = new StringBuilder();
- authorization.Append(Protocol.AuthorizationHeaderScheme);
- authorization.Append(" ");
- foreach (var pair in fields) {
- string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key);
- string value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value);
- authorization.Append(key);
- authorization.Append("=\"");
- authorization.Append(value);
- authorization.Append("\",");
- }
- authorization.Length--; // remove trailing comma
-
- httpRequest.Headers.Add(HttpRequestHeader.Authorization, authorization.ToString());
+ httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields));
if (hasEntity) {
// WARNING: We only set up the request stream for the caller if there is
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs
index 3fefed5..4e10f06 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapResourceServerChannel.cs
@@ -10,10 +10,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
+ using System.Net.Mime;
using System.Text;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuthWrap.Messages;
/// <summary>
/// The channel for the OAuth WRAP protocol.
@@ -26,21 +28,6 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
private static readonly Version[] Versions = Protocol.AllVersions.Select(v => v.Version).ToArray();
/// <summary>
- /// A character array containing just the = character.
- /// </summary>
- private static readonly char[] EqualsArray = new char[] { '=' };
-
- /// <summary>
- /// A character array containing just the , character.
- /// </summary>
- private static readonly char[] CommaArray = new char[] { ',' };
-
- /// <summary>
- /// A character array containing just the " character.
- /// </summary>
- private static readonly char[] QuoteArray = new char[] { '"' };
-
- /// <summary>
/// Initializes a new instance of the <see cref="OAuthWrapResourceServerChannel"/> class.
/// </summary>
protected internal OAuthWrapResourceServerChannel()
@@ -48,25 +35,6 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
// TODO: add signing (authenticated request) binding element.
}
- private IEnumerable<KeyValuePair<string, string>> ParseAuthorizationHeader(string authorizationHeader) {
- const string Prefix = Protocol.HttpAuthorizationScheme + " ";
- if (authorizationHeader != null) {
- string[] authorizationSections = authorizationHeader.Split(';'); // TODO: is this the right delimiter?
- foreach (string authorization in authorizationSections) {
- if (authorizationHeader.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) {
- string data = authorizationHeader.Substring(Prefix.Length);
- return from element in data.Split(CommaArray)
- let parts = element.Split(EqualsArray, 2)
- let key = Uri.UnescapeDataString(parts[0])
- let value = Uri.UnescapeDataString(parts[1].Trim(QuoteArray))
- select new KeyValuePair<string, string>(key, value);
- }
- }
- }
-
- return Enumerable.Empty<KeyValuePair<string, string>>();
- }
-
/// <summary>
/// Gets the protocol message that may be embedded in the given HTTP request.
/// </summary>
@@ -75,12 +43,42 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// The deserialized message, if one is found. Null otherwise.
/// </returns>
protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) {
- var fields = new Dictionary<string, string>();
-
// First search the Authorization header.
- var data = this.ParseAuthorizationHeader(request.Headers[HttpRequestHeader.Authorization])
- .ToDictionary(pair => pair.Key, pair => pair.Value);
- if (data.Count > 0) {
+ var fields = MessagingUtilities.ParseAuthorizationHeader(
+ Protocol.HttpAuthorizationScheme,
+ request.Headers[HttpRequestHeader.Authorization]).ToDictionary();
+
+ // Failing that, try the query string (for GET or POST or any other method)
+ if (fields.Count == 0) {
+ if (request.QueryStringBeforeRewriting["oauth_token"] != null) {
+ // We're only interested in the oauth_token parameter -- not the others that can appear in an Authorization header.
+ // Note that we intentionally change the name of the key here
+ // because depending on the method used to obtain the token, the token's key changes
+ // but we need to consolidate to one name so it works with the rest of the system.
+ fields.Add("token", request.QueryStringBeforeRewriting["oauth_token"]);
+ }
+ }
+
+ // Failing that, scan the entity
+ if (fields.Count == 0) {
+ // The spec calls out that this is allowed only for these three HTTP methods.
+ if (request.HttpMethod == "POST" || request.HttpMethod == "DELETE" || request.HttpMethod == "PUT") {
+ if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) {
+ var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]);
+ if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) {
+ if (request.Form["oauth_token"] != null) {
+ // We're only interested in the oauth_token parameter -- not the others that can appear in an Authorization header.
+ // Note that we intentionally change the name of the key here
+ // because depending on the method used to obtain the token, the token's key changes
+ // but we need to consolidate to one name so it works with the rest of the system.
+ fields.Add("token", request.Form["oauth_token"]);
+ }
+ }
+ }
+ }
+ }
+
+ if (fields.Count > 0) {
MessageReceivingEndpoint recipient;
try {
recipient = request.GetRecipient();
@@ -97,7 +95,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
return message;
}
- return base.ReadFromRequestCore(request);
+ return null;
}
/// <summary>
@@ -126,8 +124,23 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// This method implements spec OAuth V1.0 section 5.3.
/// </remarks>
protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
- throw new NotImplementedException();
- }
+ var webResponse = new OutgoingWebResponse();
+
+ // The only direct response from a resource server is a 401 Unauthorized error.
+ var unauthorizedResponse = response as UnauthorizedResponse;
+ ErrorUtilities.VerifyInternal(unauthorizedResponse != null, "Only unauthorized responses are expected.");
+ // First initialize based on the specifics within the message.
+ var httpResponse = response as IHttpDirectResponse;
+ webResponse.Status = httpResponse != null ? httpResponse.HttpStatusCode : HttpStatusCode.Unauthorized;
+ foreach (string headerName in httpResponse.Headers) {
+ webResponse.Headers.Add(headerName);
+ }
+
+ // Now serialize all the message parts into the WWW-Authenticate header.
+ var fields = this.MessageDescriptions.GetAccessor(response);
+ webResponse.Headers[HttpResponseHeader.WwwAuthenticate] = MessagingUtilities.AssembleAuthorizationHeader(Protocol.HttpAuthorizationScheme, fields);
+ return webResponse;
+ }
}
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UnauthorizedResponse.cs
index 901b6ff..ad6d851 100644
--- a/src/DotNetOpenAuth/OAuthWrap/Messages/UnauthorizedResponse.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UnauthorizedResponse.cs
@@ -5,7 +5,10 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuthWrap.Messages {
+ using System;
+ using System.Diagnostics.Contracts;
using System.Net;
+ using System.Text;
using DotNetOpenAuth.Messaging;
/// <summary>
@@ -19,6 +22,18 @@ namespace DotNetOpenAuth.OAuthWrap.Messages {
/// <param name="request">The request.</param>
internal UnauthorizedResponse(IDirectedProtocolMessage request)
: base(request) {
+ this.Realm = "Service";
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="exception">The exception.</param>
+ internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception)
+ : this(request) {
+ Contract.Requires<ArgumentNullException>(exception != null, "exception");
+ this.ErrorMessage = exception.Message;
}
#region IHttpDirectResponse Members
@@ -43,5 +58,11 @@ namespace DotNetOpenAuth.OAuthWrap.Messages {
}
#endregion
+
+ [MessagePart("error")]
+ internal string ErrorMessage { get; set; }
+
+ [MessagePart("realm")]
+ internal string Realm { get; set; }
}
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs b/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs
index e701137..6370b9d 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ResourceServer.cs
@@ -75,20 +75,11 @@ namespace DotNetOpenAuth.OAuthWrap {
throw ErrorUtilities.ThrowProtocol("Missing access token.");
}
} catch (ProtocolException ex) {
- var unauthorizedError = new OutgoingWebResponse {
- Status = HttpStatusCode.Unauthorized,
- };
-
- var authenticate = new StringBuilder();
- authenticate.Append(Protocol.HttpAuthorizationScheme + " ");
- authenticate.AppendFormat("realm='{0}'", "Service");
- authenticate.Append(",");
- authenticate.AppendFormat("error=\"{0}\"", ex.Message);
- unauthorizedError.Headers.Add(HttpResponseHeader.WwwAuthenticate, authenticate.ToString());
+ var response = new UnauthorizedResponse(null, ex);
username = null;
scope = null;
- return unauthorizedError;
+ return this.Channel.PrepareResponse(response);
}
}
}