diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2011-07-27 05:52:47 -0600 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2011-07-27 05:52:47 -0600 |
commit | fc69b9a9953057a19d98af0613ab6be496a35078 (patch) | |
tree | c925c8bd01d028296041a106c0f1b1f64932806b /src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs | |
parent | d416ff734cea0ed41c275e904a0a09834cc3f079 (diff) | |
download | DotNetOpenAuth-fc69b9a9953057a19d98af0613ab6be496a35078.zip DotNetOpenAuth-fc69b9a9953057a19d98af0613ab6be496a35078.tar.gz DotNetOpenAuth-fc69b9a9953057a19d98af0613ab6be496a35078.tar.bz2 |
Extracted OpenID RP UI project.
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs')
-rw-r--r-- | src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs new file mode 100644 index 0000000..42bfbde --- /dev/null +++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs @@ -0,0 +1,242 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdAjaxRelyingParty.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Mime; + using System.Text; + using System.Web; + using System.Web.Script.Serialization; + + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.UI; + + /// <summary> + /// Provides the programmatic facilities to act as an AJAX-enabled OpenID relying party. + /// </summary> + public class OpenIdAjaxRelyingParty : OpenIdRelyingParty { + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. + /// </summary> + public OpenIdAjaxRelyingParty() { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. + /// </summary> + /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> + public OpenIdAjaxRelyingParty(IOpenIdApplicationStore applicationStore) + : base(applicationStore) { + Reporting.RecordFeatureUse(this); + } + + /// <summary> + /// Generates AJAX-ready authentication requests that can satisfy the requirements of some OpenID Identifier. + /// </summary> + /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> + /// <param name="realm">The shorest URL that describes this relying party web site's address. + /// For example, if your login page is found at https://www.example.com/login.aspx, + /// your realm would typically be https://www.example.com/.</param> + /// <param name="returnToUrl">The URL of the login page, or the page prepared to receive authentication + /// responses from the OpenID Provider.</param> + /// <returns> + /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. + /// Never null, but may be empty. + /// </returns> + /// <remarks> + /// <para>Any individual generated request can satisfy the authentication. + /// The generated requests are sorted in preferred order. + /// Each request is generated as it is enumerated to. Associations are created only as + /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> + /// <para>No exception is thrown if no OpenID endpoints were discovered. + /// An empty enumerable is returned instead.</para> + /// </remarks> + public override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { + var requests = base.CreateRequests(userSuppliedIdentifier, realm, returnToUrl); + + // Alter the requests so that have AJAX characteristics. + // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). + // Since we're gathering OPs to try one after the other, just take the first choice of each OP + // and don't try it multiple times. + requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); + + // Configure each generated request. + int reqIndex = 0; + foreach (var req in requests) { + // Inform ourselves in return_to that we're in a popup. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.UIPopupCallbackKey, "1"); + + if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { + // Inform the OP that we'll be using a popup window consistent with the UI extension. + req.AddExtension(new UIRequest()); + + // Provide a hint for the client javascript about whether the OP supports the UI extension. + // This is so the window can be made the correct size for the extension. + // If the OP doesn't advertise support for the extension, the javascript will use + // a bigger popup window. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.PopupUISupportedJSHint, "1"); + } + + req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); + + // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter + if (OpenIdElement.Configuration.RelyingParty.PreserveUserSuppliedIdentifier) { + if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { + req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString); + } + } + + // Our javascript needs to let the user know which endpoint responded. So we force it here. + // This gives us the info even for 1.0 OPs and 2.0 setup_required responses. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); + + // Inform ourselves in return_to that we're in a popup or iframe. + req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.UIPopupCallbackKey, "1"); + + // We append a # at the end so that if the OP happens to support it, + // the OpenID response "query string" is appended after the hash rather than before, resulting in the + // browser being super-speedy in closing the popup window since it doesn't try to pull a newer version + // of the static resource down from the server merely because of a changed URL. + // http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html + ////TODO: + + yield return req; + } + } + + /// <summary> + /// Serializes discovery results on some <i>single</i> identifier on behalf of Javascript running on the browser. + /// </summary> + /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> + /// <returns> + /// The JSON result to return to the user agent. + /// </returns> + /// <remarks> + /// We prepare a JSON object with this interface: + /// <code> + /// class jsonResponse { + /// string claimedIdentifier; + /// Array requests; // never null + /// string error; // null if no error + /// } + /// </code> + /// Each element in the requests array looks like this: + /// <code> + /// class jsonAuthRequest { + /// string endpoint; // URL to the OP endpoint + /// string immediate; // URL to initiate an immediate request + /// string setup; // URL to initiate a setup request. + /// } + /// </code> + /// </remarks> + public OutgoingWebResponse AsAjaxDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires<ArgumentNullException>(requests != null); + + var serializer = new JavaScriptSerializer(); + return new OutgoingWebResponse { + Body = serializer.Serialize(this.AsJsonDiscoveryResult(requests)), + }; + } + + /// <summary> + /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries + /// an AJAX-aware OpenID control. + /// </summary> + /// <param name="requests">The discovery results to serialize as a JSON response.</param> + /// <returns> + /// The JSON result to return to the user agent. + /// </returns> + public string AsAjaxPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires<ArgumentNullException>(requests != null); + + var serializer = new JavaScriptSerializer(); + string json = serializer.Serialize(this.AsJsonPreloadedDiscoveryResult(requests)); + + string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + json + ");"; + return script; + } + + /// <summary> + /// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page. + /// </summary> + /// <param name="requests">The discovery results from just <i>one</i> identifier to serialize as a JSON response.</param> + /// <returns>A JSON object, not yet serialized.</returns> + internal object AsJsonDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires<ArgumentNullException>(requests != null); + + requests = requests.CacheGeneratedResults(); + + if (requests.Any()) { + return new { + claimedIdentifier = (string)requests.First().ClaimedIdentifier, + requests = requests.Select(req => new { + endpoint = req.Provider.Uri.AbsoluteUri, + immediate = this.GetRedirectUrl(req, true), + setup = this.GetRedirectUrl(req, false), + }).ToArray() + }; + } else { + return new { + requests = new object[0], + error = OpenIdStrings.OpenIdEndpointNotFound, + }; + } + } + + /// <summary> + /// Serializes discovery on a set of identifiers for preloading into an HTML page that carries + /// an AJAX-aware OpenID control. + /// </summary> + /// <param name="requests">The discovery results to serialize as a JSON response.</param> + /// <returns> + /// A JSON object, not yet serialized to a string. + /// </returns> + private object AsJsonPreloadedDiscoveryResult(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires<ArgumentNullException>(requests != null); + + // We prepare a JSON object with this interface: + // Array discoveryWrappers; + // Where each element in the above array has this interface: + // class discoveryWrapper { + // string userSuppliedIdentifier; + // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier) + // } + var json = (from request in requests + group request by request.DiscoveryResult.UserSuppliedIdentifier into requestsByIdentifier + select new { + userSuppliedIdentifier = (string)requestsByIdentifier.Key, + discoveryResult = this.AsJsonDiscoveryResult(requestsByIdentifier), + }).ToArray(); + + return json; + } + + /// <summary> + /// Gets the full URL that carries an OpenID message, even if it exceeds the normal maximum size of a URL, + /// for purposes of sending to an AJAX component running in the browser. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="immediate"><c>true</c>to create a checkid_immediate request; + /// <c>false</c> to create a checkid_setup request.</param> + /// <returns>The absolute URL that carries the entire OpenID message.</returns> + private Uri GetRedirectUrl(IAuthenticationRequest request, bool immediate) { + Contract.Requires<ArgumentNullException>(request != null); + + request.Mode = immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup; + return request.RedirectingResponse.GetDirectUriRequest(this.Channel); + } + } +} |