diff options
Diffstat (limited to 'src')
4 files changed, 280 insertions, 329 deletions
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs index 84d5c0f..e2ddf16 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs @@ -25,18 +25,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> public class OpenIdAjaxRelyingParty : OpenIdRelyingParty { /// <summary> - /// The <see cref="OpenIdRelyingParty"/> instance used to process authentication responses - /// without verifying the assertion or consuming nonces. - /// </summary> - private OpenIdRelyingParty nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying(); - - /// <summary> - /// A dictionary of extension response types and the javascript member - /// name to map them to on the user agent. - /// </summary> - private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); - - /// <summary> /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. /// </summary> public OpenIdAjaxRelyingParty() { @@ -112,6 +100,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // 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; } } @@ -150,136 +145,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Allows an OpenID extension to read data out of an unverified positive authentication assertion - /// and send it down to the client browser so that Javascript running on the page can perform - /// some preprocessing on the extension data. - /// </summary> - /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> - /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> - /// <remarks> - /// This method should be called before <see cref="ProcessAjaxOpenIdResponse()"/>. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] - public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); - ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); - foreach (var ext in this.clientScriptExtensions.Keys) { - ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); - } - this.clientScriptExtensions.Add(typeof(T), propertyName); - } - - /// <summary> - /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. - /// </summary> - /// <returns>The HTTP response to send to this HTTP request.</returns> - /// <remarks> - /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> - /// </remarks> - public OutgoingWebResponse ProcessAjaxOpenIdResponse() { - Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - return this.ProcessAjaxOpenIdResponse(this.Channel.GetRequestFromContext()); - } - - /// <summary> - /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. - /// </summary> - /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> - /// <returns>The HTTP response to send to this HTTP request.</returns> - public OutgoingWebResponse ProcessAjaxOpenIdResponse(HttpRequestInfo request) { - Contract.Requires<ArgumentNullException>(request != null); - Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - - string extensionsJson = null; - var authResponse = this.nonVerifyingRelyingParty.GetResponse(); - ErrorUtilities.VerifyProtocol(authResponse != null, "OpenID popup window or iframe did not recognize an OpenID response in the request."); - Logger.OpenId.DebugFormat("AJAX (iframe) callback from OP: {0}", request.Url); - Logger.Controls.DebugFormat( - "The MVC controller checked for an authentication response from a popup window or iframe using a non-verifying RP and found: {0}", - authResponse.Status); - if (authResponse.Status == AuthenticationStatus.Authenticated) { - var extensionsDictionary = new Dictionary<string, string>(); - foreach (var pair in this.clientScriptExtensions) { - IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); - if (extension == null) { - continue; - } - var positiveResponse = (PositiveAuthenticationResponse)authResponse; - string js = extension.InitializeJavaScriptData(positiveResponse.Response); - if (!string.IsNullOrEmpty(js)) { - extensionsDictionary[pair.Value] = js; - } - } - - extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); - } - - string payload = "document.URL"; - if (request.HttpMethod == "POST") { - // Promote all form variables to the query string, but since it won't be passed - // to any server (this is a javascript window-to-window transfer) the length of - // it can be arbitrarily long, whereas it was POSTed here probably because it - // was too long for HTTP transit. - UriBuilder payloadUri = new UriBuilder(request.Url); - payloadUri.AppendQueryArgs(request.Form.ToDictionary()); - payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); - } - - if (!string.IsNullOrEmpty(extensionsJson)) { - payload += ", " + extensionsJson; - } - - return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")"); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected override void Dispose(bool disposing) { - this.nonVerifyingRelyingParty.Dispose(); - base.Dispose(disposing); - } - - /// <summary> - /// Invokes a method on a parent frame or window and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the parent window, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns> - private static OutgoingWebResponse InvokeParentPageScript(string methodCall) { - Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(methodCall)); - - Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall); - StringBuilder builder = new StringBuilder(); - builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--"); - builder.AppendLine("//<![CDATA["); - builder.Append(@" var inPopup = !window.frameElement; - var objSrc = inPopup ? window.opener : window.frameElement; -"); - - // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, - // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already - // whether to call window.self.close() after the call. - string htmlFormat = @" if (inPopup) {{ - objSrc.{0}; - window.self.close(); - }} else {{ - objSrc.{0}; - }}"; - builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall); - builder.AppendLine("//]]>--></script>"); - builder.AppendLine("</body></html>"); - - var response = new OutgoingWebResponse(); - response.Body = builder.ToString(); - response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString()); - return response; - } - - /// <summary> /// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page. /// </summary> /// <param name="requests">The authentication requests.</param> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index d363b93..1225540 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -12,7 +12,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System.ComponentModel; 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 DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; @@ -54,6 +58,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2); /// <summary> + /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property. + /// </summary> + private OpenIdRelyingParty nonVerifyingRelyingParty; + + /// <summary> + /// The lock to obtain when initializing the <see cref="nonVerifyingRelyingParty"/> member. + /// </summary> + private object nonVerifyingRelyingPartyInitLock = new object(); + + /// <summary> + /// A dictionary of extension response types and the javascript member + /// name to map them to on the user agent. + /// </summary> + private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); + + /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. /// </summary> private RelyingPartySecuritySettings securitySettings; @@ -270,6 +290,24 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { internal AssociationManager AssociationManager { get; private set; } /// <summary> + /// Gets the <see cref="OpenIdRelyingParty"/> instance used to process authentication responses + /// without verifying the assertion or consuming nonces. + /// </summary> + protected OpenIdRelyingParty NonVerifyingRelyingParty { + get { + if (this.nonVerifyingRelyingParty == null) { + lock (this.nonVerifyingRelyingPartyInitLock) { + if (this.nonVerifyingRelyingParty == null) { + this.nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying(); + } + } + } + + return this.nonVerifyingRelyingParty; + } + } + + /// <summary> /// Creates an authentication request to verify that a user controls /// some given Identifier. /// </summary> @@ -534,6 +572,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <returns>The HTTP response to send to this HTTP request.</returns> + /// <remarks> + /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> + /// </remarks> + public OutgoingWebResponse ProcessResponseFromPopup() { + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + return this.ProcessResponseFromPopup(this.Channel.GetRequestFromContext()); + } + + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> + /// <returns>The HTTP response to send to this HTTP request.</returns> + public OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + return this.ProcessResponseFromPopup(request, null); + } + #region IDisposable Members /// <summary> @@ -581,6 +645,86 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Allows an OpenID extension to read data out of an unverified positive authentication assertion + /// and send it down to the client browser so that Javascript running on the page can perform + /// some preprocessing on the extension data. + /// </summary> + /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> + /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> + /// <remarks> + /// This method should be called before <see cref="ProcessResponseFromPopup()"/>. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] + internal void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); + ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); + foreach (var ext in this.clientScriptExtensions.Keys) { + ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); + } + this.clientScriptExtensions.Add(typeof(T), propertyName); + } + + /// <summary> + /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication. + /// </summary> + /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param> + /// <param name="callback">The callback fired after the response status has been determined but before the Javascript response is formulated.</param> + /// <returns> + /// The HTTP response to send to this HTTP request. + /// </returns> + internal OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request, Action<AuthenticationStatus> callback) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); + + string extensionsJson = null; + var authResponse = this.NonVerifyingRelyingParty.GetResponse(); + ErrorUtilities.VerifyProtocol(authResponse != null, "OpenID popup window or iframe did not recognize an OpenID response in the request."); + + // Give the caller a chance to notify the hosting page and fill up the clientScriptExtensions collection. + if (callback != null) { + callback(authResponse.Status); + } + + Logger.OpenId.DebugFormat("Popup or iframe callback from OP: {0}", request.Url); + Logger.Controls.DebugFormat( + "An authentication response was found in a popup window or iframe using a non-verifying RP with status: {0}", + authResponse.Status); + if (authResponse.Status == AuthenticationStatus.Authenticated) { + var extensionsDictionary = new Dictionary<string, string>(); + foreach (var pair in this.clientScriptExtensions) { + IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); + if (extension == null) { + continue; + } + var positiveResponse = (PositiveAuthenticationResponse)authResponse; + string js = extension.InitializeJavaScriptData(positiveResponse.Response); + if (!string.IsNullOrEmpty(js)) { + extensionsDictionary[pair.Value] = js; + } + } + + extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); + } + + string payload = "document.URL"; + if (request.HttpMethod == "POST") { + // Promote all form variables to the query string, but since it won't be passed + // to any server (this is a javascript window-to-window transfer) the length of + // it can be arbitrarily long, whereas it was POSTed here probably because it + // was too long for HTTP transit. + UriBuilder payloadUri = new UriBuilder(request.Url); + payloadUri.AppendQueryArgs(request.Form.ToDictionary()); + payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); + } + + if (!string.IsNullOrEmpty(extensionsJson)) { + payload += ", " + extensionsJson; + } + + return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")"); + } + + /// <summary> /// Performs discovery on the specified identifier. /// </summary> /// <param name="identifier">The identifier to discover services for.</param> @@ -622,6 +766,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (disposing) { + if (this.nonVerifyingRelyingParty != null) { + this.nonVerifyingRelyingParty.Dispose(); + this.nonVerifyingRelyingParty = null; + } + // Tear off the instance member as a local variable for thread safety. IDisposable disposableChannel = this.channel as IDisposable; if (disposableChannel != null) { @@ -631,6 +780,42 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Invokes a method on a parent frame or window and closes the calling popup window if applicable. + /// </summary> + /// <param name="methodCall">The method to call on the parent window, including + /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> + /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns> + private static OutgoingWebResponse InvokeParentPageScript(string methodCall) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(methodCall)); + + Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall); + StringBuilder builder = new StringBuilder(); + builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--"); + builder.AppendLine("//<![CDATA["); + builder.Append(@" var inPopup = !window.frameElement; + var objSrc = inPopup ? window.opener : window.frameElement; +"); + + // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, + // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already + // whether to call window.self.close() after the call. + string htmlFormat = @" if (inPopup) {{ + objSrc.{0}; + window.self.close(); + }} else {{ + objSrc.{0}; + }}"; + builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall); + builder.AppendLine("//]]>--></script>"); + builder.AppendLine("</body></html>"); + + var response = new OutgoingWebResponse(); + response.Body = builder.ToString(); + response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString()); + return response; + } + + /// <summary> /// Called by derived classes when behaviors are added or removed. /// </summary> /// <param name="sender">The collection being modified.</param> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index c6c0a5d..7153aa2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -86,11 +86,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.None; /// <summary> - /// Backing field for the <see cref="RelyingPartyNonVerifying"/> property. - /// </summary> - private static OpenIdRelyingParty relyingPartyNonVerifying; - - /// <summary> /// The authentication response that just came in. /// </summary> private IAuthenticationResponse authenticationResponse; @@ -102,12 +97,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private string discoveryResult; /// <summary> - /// A dictionary of extension response types and the javascript member - /// name to map them to on the user agent. - /// </summary> - private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); - - /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingPartyAjaxControlBase"/> class. /// </summary> protected OpenIdRelyingPartyAjaxControlBase() { @@ -152,6 +141,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. + /// </summary> + /// <value> + /// The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file. + /// </value> + /// <remarks> + /// A performance optimization would be to store off the + /// instance as a static member in your web site and set it + /// to this property in your <see cref="Control.Load">Page.Load</see> + /// event since instantiating these instances can be expensive on + /// heavily trafficked web pages. + /// </remarks> + public override OpenIdRelyingParty RelyingParty { + get { + return base.RelyingParty; + } + + set { + // Make sure we get an AJAX-ready instance. + ErrorUtilities.VerifyArgument(value is OpenIdAjaxRelyingParty, OpenIdStrings.TypeMustImplementX, typeof(OpenIdAjaxRelyingParty).Name); + base.RelyingParty = value; + } + } + + /// <summary> /// Gets the completed authentication response. /// </summary> public IAuthenticationResponse AuthenticationResponse { @@ -193,22 +207,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). + /// Gets the relying party as its AJAX type. /// </summary> - /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value> - protected abstract string OpenIdAuthDataFormKey { get; } + protected OpenIdAjaxRelyingParty AjaxRelyingParty { + get { return (OpenIdAjaxRelyingParty)this.RelyingParty; } + } /// <summary> - /// Gets the relying party to use when verification of incoming messages is NOT wanted. + /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). /// </summary> - private static OpenIdRelyingParty RelyingPartyNonVerifying { - get { - if (relyingPartyNonVerifying == null) { - relyingPartyNonVerifying = OpenIdRelyingParty.CreateNonVerifying(); - } - return relyingPartyNonVerifying; - } - } + /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value> + protected abstract string OpenIdAuthDataFormKey { get; } /// <summary> /// Gets or sets a value indicating whether an authentication in the page's view state @@ -232,11 +241,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); - ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); - foreach (var ext in this.clientScriptExtensions.Keys) { - ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); - } - this.clientScriptExtensions.Add(typeof(T), propertyName); + this.RelyingParty.RegisterClientScriptExtension<T>(propertyName); } #region ICallbackEventHandler Members @@ -263,27 +268,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="identifier">The identifier to create a request for.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { - // If this control is actually a member of another OpenID RP control, - // delegate creation of requests to the parent control. - var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault(); - if (parentOwner != null) { - return parentOwner.CreateRequests(identifier); - } else { - // We delegate all our logic to another method, since invoking base. methods - // within an iterator method results in unverifiable code. - return this.CreateRequestsCore(base.CreateRequests(identifier)); - } - } - - /// <summary> /// Returns the results of a callback event that targets a control. /// </summary> /// <returns>The result of the callback.</returns> @@ -309,6 +293,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <param name="store">The store to pass to the relying party constructor.</param> + /// <returns>The instantiated relying party.</returns> + protected override OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) { + return new OpenIdAjaxRelyingParty(store); + } + + /// <summary> /// Pre-discovers an identifier and makes the results available to the /// user agent for javascript as soon as the page loads. /// </summary> @@ -420,48 +413,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Notifies the user agent via an AJAX response of a completed authentication attempt. /// </summary> protected override void ScriptClosingPopupOrIFrame() { - Logger.OpenId.DebugFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url); - string extensionsJson = null; - - var authResponse = RelyingPartyNonVerifying.GetResponse(); - Logger.Controls.DebugFormat( - "The {0} control checked for an authentication response from a popup window or iframe using a non-verifying RP and found: {1}", - this.ID, - authResponse.Status); - if (authResponse.Status == AuthenticationStatus.Authenticated) { - this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. - var extensionsDictionary = new Dictionary<string, string>(); - foreach (var pair in this.clientScriptExtensions) { - IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); - if (extension == null) { - continue; - } - var positiveResponse = (PositiveAuthenticationResponse)authResponse; - string js = extension.InitializeJavaScriptData(positiveResponse.Response); - if (!string.IsNullOrEmpty(js)) { - extensionsDictionary[pair.Value] = js; - } + Action<AuthenticationStatus> callback = status => { + if (status == AuthenticationStatus.Authenticated) { + this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. } + }; - extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); - } - - string payload = "document.URL"; - if (Page.Request.HttpMethod == "POST") { - // Promote all form variables to the query string, but since it won't be passed - // to any server (this is a javascript window-to-window transfer) the length of - // it can be arbitrarily long, whereas it was POSTed here probably because it - // was too long for HTTP transit. - UriBuilder payloadUri = new UriBuilder(Page.Request.Url); - payloadUri.AppendQueryArgs(Page.Request.Form.ToDictionary()); - payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); - } - - if (!string.IsNullOrEmpty(extensionsJson)) { - payload += ", " + extensionsJson; - } + OutgoingWebResponse response = this.RelyingParty.ProcessResponseFromPopup( + this.RelyingParty.Channel.GetRequestFromContext(), + callback); - this.CallbackUserAgentMethod("dnoa_internal.processAuthorizationResult(" + payload + ")"); + response.Send(); } /// <summary> @@ -548,47 +510,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="requests">The authentication requests to prepare.</param> - /// <returns> - /// A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. - /// </returns> - private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) { - ErrorUtilities.VerifyArgumentNotNull(requests, "requests"); // NO CODE CONTRACTS! (yield return used here) - - // Configure each generated request. - int reqIndex = 0; - foreach (var req in requests) { - req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); - - // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter - if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { - Identifier userSuppliedIdentifier = ((AuthenticationRequest)req).DiscoveryResult.UserSuppliedIdentifier; - 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(OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); - req.SetUntrustedCallbackArgument(ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); - - // Inform ourselves in return_to that we're in a popup or iframe. - req.SetUntrustedCallbackArgument(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> /// Constructs a function that will initiate an AJAX callback. /// </summary> /// <param name="async">if set to <c>true</c> causes the AJAX callback to be a little more asynchronous. Note that <c>false</c> does not mean the call is absolutely synchronous.</param> @@ -615,33 +536,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, - /// and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - private void CallbackUserAgentMethod(string methodCall) { - Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall); - Page.Response.Write(@"<html><body><script language='javascript'> - var inPopup = !window.frameElement; - var objSrc = inPopup ? window.opener : window.frameElement; -"); - - // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, - // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already - // whether to call window.self.close() after the call. - string htmlFormat = @" if (inPopup) {{ - objSrc.{0}; - window.self.close(); - }} else {{ - objSrc.{0}; - }} -</script></body></html>"; - Page.Response.Write(string.Format(CultureInfo.InvariantCulture, htmlFormat, methodCall)); - Page.Response.End(); - } - - /// <summary> /// Sets the window.aspnetapppath variable on the user agent so that cookies can be set with the proper path. /// </summary> private void SetWebAppPathOnUserAgent() { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 672bbfa..838b749 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -297,10 +297,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// heavily trafficked web pages. /// </remarks> [Browsable(false)] - public OpenIdRelyingParty RelyingParty { + public virtual OpenIdRelyingParty RelyingParty { get { if (this.relyingParty == null) { this.relyingParty = this.CreateRelyingParty(); + this.ConfigureRelyingParty(this.relyingParty); this.relyingPartyOwned = true; } return this.relyingParty; @@ -560,8 +561,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { protected internal virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { Contract.Requires<ArgumentNullException>(identifier != null); - // Delegate to a private method to keep 'yield return' and Code Contract separate. - return this.CreateRequestsCore(identifier); + // If this control is actually a member of another OpenID RP control, + // delegate creation of requests to the parent control. + var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault(); + if (parentOwner != null) { + return parentOwner.CreateRequests(identifier); + } else { + // Delegate to a private method to keep 'yield return' and Code Contract separate. + return this.CreateRequestsCore(identifier); + } } /// <summary> @@ -638,6 +646,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Notifies the user agent via an AJAX response of a completed authentication attempt. + /// </summary> + protected virtual void ScriptClosingPopupOrIFrame() { + this.RelyingParty.ProcessResponseFromPopup(); + } + + /// <summary> /// Called when the <see cref="Identifier"/> property is changed. /// </summary> protected virtual void OnIdentifierChanged() { @@ -773,29 +788,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Creates the relying party instance used to generate authentication requests. /// </summary> /// <returns>The instantiated relying party.</returns> - protected virtual OpenIdRelyingParty CreateRelyingParty() { - return this.CreateRelyingParty(true); + protected OpenIdRelyingParty CreateRelyingParty() { + IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); + return this.CreateRelyingParty(store); } /// <summary> /// Creates the relying party instance used to generate authentication requests. /// </summary> - /// <param name="verifySignature"> - /// A value indicating whether message protections should be applied to the processed messages. - /// Use <c>false</c> to postpone verification to a later time without invalidating nonces. - /// </param> + /// <param name="store">The store to pass to the relying party constructor.</param> /// <returns>The instantiated relying party.</returns> - protected virtual OpenIdRelyingParty CreateRelyingParty(bool verifySignature) { - IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); - var rp = verifySignature ? new OpenIdRelyingParty(store) : OpenIdRelyingParty.CreateNonVerifying(); + protected virtual OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) { + return new OpenIdRelyingParty(store); + } + + /// <summary> + /// Configures the relying party. + /// </summary> + /// <param name="relyingParty">The relying party.</param> + protected virtual void ConfigureRelyingParty(OpenIdRelyingParty relyingParty) { + Contract.Requires<ArgumentNullException>(relyingParty != null); // Only set RequireSsl to true, as we don't want to override // a .config setting of true with false. if (this.RequireSsl) { - rp.SecuritySettings.RequireSsl = true; + relyingParty.SecuritySettings.RequireSsl = true; } - - return rp; } /// <summary> @@ -845,21 +863,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Wires the popup window to close itself and pass the authentication result to the parent window. - /// </summary> - protected virtual void ScriptClosingPopupOrIFrame() { - StringBuilder startupScript = new StringBuilder(); - startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL);"); - startupScript.AppendLine("window.close();"); - - this.Page.ClientScript.RegisterStartupScript(typeof(OpenIdRelyingPartyControlBase), "loginPopupClose", startupScript.ToString(), true); - - // TODO: alternately we should probably take over rendering this page here to avoid - // a lot of unnecessary work on the server and possible momentary display of the - // page in the popup window. - } - - /// <summary> /// Creates the identifier-persisting cookie, either for saving or deleting. /// </summary> /// <param name="response">The positive authentication response; or <c>null</c> to clear the cookie.</param> @@ -943,7 +946,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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()); + // But beware that the extension MAY have already been added if we're using + // the OpenIdAjaxRelyingParty class. + if (!((AuthenticationRequest)req).Extensions.OfType<UIRequest>().Any()) { + 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. |