diff options
Diffstat (limited to 'src')
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<string, string>. /// </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); } } } |