diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-13 19:58:24 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-05-13 19:58:24 -0700 |
commit | 91b551a888d3cae98cf5f1053c6f5c7141db4a3f (patch) | |
tree | a383f282986df00dd2e8aa98e12e102c8cee665f | |
parent | 448c6dc2e352a49054358907abec3f9bfca3cf13 (diff) | |
download | DotNetOpenAuth-91b551a888d3cae98cf5f1053c6f5c7141db4a3f.zip DotNetOpenAuth-91b551a888d3cae98cf5f1053c6f5c7141db4a3f.tar.gz DotNetOpenAuth-91b551a888d3cae98cf5f1053c6f5c7141db4a3f.tar.bz2 |
Facebook OAuth 2.0 client now works.
-rw-r--r-- | samples/DotNetOpenAuth.ApplicationBlock/FacebookClient.cs | 9 | ||||
-rw-r--r-- | samples/OAuthConsumer/Default.aspx | 1 | ||||
-rw-r--r-- | samples/OAuthConsumer/Facebook.aspx.cs | 5 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/Channel.cs | 10 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/MessagingUtilities.cs | 24 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs | 11 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs | 20 | ||||
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs | 3 |
8 files changed, 67 insertions, 16 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/FacebookClient.cs b/samples/DotNetOpenAuth.ApplicationBlock/FacebookClient.cs index be4df21..b7df5dd 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/FacebookClient.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/FacebookClient.cs @@ -1,12 +1,17 @@ -using System.Web; -using DotNetOpenAuth.OAuthWrap; +//----------------------------------------------------------------------- +// <copyright file="FacebookClient.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; using System.Linq; using System.Text; + using System.Web; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuthWrap; public class FacebookClient : WebAppClient { private static readonly AuthorizationServerDescription FacebookDescription = new AuthorizationServerDescription { diff --git a/samples/OAuthConsumer/Default.aspx b/samples/OAuthConsumer/Default.aspx index c952877..f3bceb6 100644 --- a/samples/OAuthConsumer/Default.aspx +++ b/samples/OAuthConsumer/Default.aspx @@ -9,6 +9,7 @@ <li><a href="GoogleAddressBook.aspx">Download your Gmail address book</a></li> <li><a href="Twitter.aspx">Get your Twitter updates</a></li> <li><a href="SignInWithTwitter.aspx">Sign In With Twitter</a></li> + <li><a href="Facebook.aspx">Sign in with Facebook</a></li> <li><a href="SampleWcf.aspx">Interop with Service Provider sample using WCF w/ OAuth</a></li> </ul> </asp:Content> diff --git a/samples/OAuthConsumer/Facebook.aspx.cs b/samples/OAuthConsumer/Facebook.aspx.cs index c73f120..3553f0e 100644 --- a/samples/OAuthConsumer/Facebook.aspx.cs +++ b/samples/OAuthConsumer/Facebook.aspx.cs @@ -2,14 +2,15 @@ using System; using System.Collections.Generic; using System.Configuration; + using System.IO; using System.Linq; + using System.Net; using System.Web; + using System.Web.Script.Serialization; using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.ApplicationBlock; using DotNetOpenAuth.OAuthWrap; - using System.Net; - using System.IO; public partial class Facebook : System.Web.UI.Page { private static readonly FacebookClient client = new FacebookClient { diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 4bba733..2e15fb5 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -40,6 +40,16 @@ namespace DotNetOpenAuth.Messaging { protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; /// <summary> + /// The content-type used for JSON serialized objects. + /// </summary> + protected internal const string JsonEncoded = "application/json"; + + /// <summary> + /// The content-type for plain text. + /// </summary> + protected internal const string PlainTextEncoded = "text/plain"; + + /// <summary> /// The content-type used on HTTP POST requests where the POST entity is a /// URL-encoded series of key=value pairs. /// This includes the <see cref="PostEntityEncoding"/> character encoding. diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 231637a..1a513ee 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -193,6 +193,30 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Strips any and all URI query parameters that serve as parts of a message. + /// </summary> + /// <param name="uri">The URI that may contain query parameters to remove.</param> + /// <param name="messageDescription">The message description whose parts should be removed from the URL.</param> + /// <returns>A cleaned URL.</returns> + internal static Uri StripMessagePartsFromQueryString(this Uri uri, MessageDescription messageDescription) { + Contract.Requires<ArgumentNullException>(uri != null, "uri"); + Contract.Requires<ArgumentNullException>(messageDescription != null, "messageDescription"); + + NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); + var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => messageDescription.Mapping.ContainsKey(key)).ToList(); + if (matchingKeys.Count > 0) { + var builder = new UriBuilder(uri); + foreach (string key in matchingKeys) { + queryArgs.Remove(key); + } + builder.Query = CreateQueryString(queryArgs.ToDictionary()); + return builder.Uri; + } else { + return uri; + } + } + + /// <summary> /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it. /// </summary> /// <param name="request">The HTTP request.</param> diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs index fb6841c..e99b2cf 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapChannel.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { using System.Linq; using System.Net; using System.Text; + using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; @@ -77,7 +78,15 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - throw new NotImplementedException(); + // The spec says direct responses should be JSON objects, but Facebook uses HttpFormUrlEncoded instead, calling it text/plain + if (response.ContentType.MediaType == JsonEncoded) { + throw new NotImplementedException(); + } else if (response.ContentType.MediaType == HttpFormUrlEncoded || response.ContentType.MediaType == PlainTextEncoded) { + string body = response.GetResponseReader().ReadToEnd(); + return HttpUtility.ParseQueryString(body).ToDictionary(); + } else { + throw ErrorUtilities.ThrowProtocol("Unexpected response Content-Type {0}", response.ContentType.MediaType); + } } /// <summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs index 4b5eca7..790d229 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs @@ -58,6 +58,16 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { public string ClientState { get; set; } /// <summary> + /// Gets or sets a value indicating whether the authorization server is + /// required to redirect the browser back to the client immediately. + /// </summary> + /// <remarks> + /// OPTIONAL. The parameter value must be set to true or false. If set to true, the authorization server MUST NOT prompt the end-user to authenticate or approve access. Instead, the authorization server attempts to establish the end-user's identity via other means (e.g. browser cookies) and checks if the end-user has previously approved an identical access request by the same client and if that access grant is still active. If the authorization server does not support an immediate check or if it is unable to establish the end-user's identity or approval status, it MUST deny the request without prompting the end-user. Defaults to false if omitted. + /// </remarks> + [MessagePart(Protocol.immediate, IsRequired = false, AllowEmpty = false)] + public bool? Immediate { get; set; } + + /// <summary> /// Gets or sets the identifier by which this client is known to the Authorization Server. /// </summary> [MessagePart(Protocol.client_id, IsRequired = true, AllowEmpty = false)] @@ -75,15 +85,5 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// </remarks> [MessagePart(Protocol.redirect_uri, IsRequired = false, AllowEmpty = false)] internal Uri Callback { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the authorization server is - /// required to redirect the browser back to the client immediately. - /// </summary> - /// <remarks> - /// OPTIONAL. The parameter value must be set to true or false. If set to true, the authorization server MUST NOT prompt the end-user to authenticate or approve access. Instead, the authorization server attempts to establish the end-user's identity via other means (e.g. browser cookies) and checks if the end-user has previously approved an identical access request by the same client and if that access grant is still active. If the authorization server does not support an immediate check or if it is unable to establish the end-user's identity or approval status, it MUST deny the request without prompting the end-user. Defaults to false if omitted. - /// </remarks> - [MessagePart(Protocol.immediate, IsRequired = false, AllowEmpty = false)] - internal bool? Immediate { get; set; } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs index 1a43d78..08af228 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs @@ -77,7 +77,8 @@ namespace DotNetOpenAuth.OAuthWrap { IMessageWithClientState response; if (this.Channel.TryReadFromRequest<IMessageWithClientState>(request, out response)) { - IAuthorizationState authorizationState = this.TokenManager.GetAuthorizationState(request.UrlBeforeRewriting, response.ClientState); + Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.UrlBeforeRewriting, this.Channel.MessageDescriptions.Get(response)); + IAuthorizationState authorizationState = this.TokenManager.GetAuthorizationState(callback, response.ClientState); ErrorUtilities.VerifyProtocol(authorizationState != null, "Unexpected OAuth authorization response received with callback and client state that does not match an expected value."); var success = response as WebAppSuccessResponse; var failure = response as WebAppFailedResponse; |