//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName, "text/javascript")] [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName, "text/css")] namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.Contracts; using System.Globalization; using System.IdentityModel.Claims; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using DotNetOpenAuth.ComponentModel; using DotNetOpenAuth.Messaging; /// /// An ASP.NET control that provides a user-friendly way of logging into a web site using OpenID. /// [ToolboxData("<{0}:OpenIdSelector runat=\"server\">")] public class OpenIdSelector : OpenIdRelyingPartyAjaxControlBase { /// /// The name of the manifest stream containing the OpenIdButtonPanel.js file. /// internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.js"; /// /// The name of the manifest stream containing the OpenIdButtonPanel.css file. /// internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.css"; /// /// The substring to append to the end of the id or name of this control to form the /// unique name of the hidden field that will carry the positive assertion on postback. /// private const string AuthDataFormKeySuffix = "_openidAuthData"; #region ViewState keys /// /// The viewstate key to use for storing the value of the property. /// private const string ButtonsViewStateKey = "Buttons"; /// /// The viewstate key to use for storing the value of the property. /// private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; #endregion #region Property defaults /// /// The default value for the property. /// private const string AuthenticatedAsToolTipDefault = "We recognize you!"; #endregion /// /// The OpenIdAjaxTextBox that remains hidden until the user clicks the OpenID button. /// private OpenIdAjaxTextBox textBox; /// /// The hidden field that will transmit the positive assertion to the RP. /// private HiddenField positiveAssertionField; /// /// Initializes a new instance of the class. /// public OpenIdSelector() { } /// /// Gets the text box where applicable. /// public OpenIdAjaxTextBox TextBox { get { this.EnsureChildControlsAreCreatedSafe(); return this.textBox; } } /// /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. /// [Browsable(true), DefaultValue(OpenIdAjaxTextBox.ThrottleDefault), Category(BehaviorCategory)] [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] public int Throttle { get { this.EnsureChildControlsAreCreatedSafe(); return this.textBox.Throttle; } set { this.EnsureChildControlsAreCreatedSafe(); this.textBox.Throttle = value; } } /// /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. /// [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] public TimeSpan Timeout { get { this.EnsureChildControlsAreCreatedSafe(); return this.textBox.Timeout; } set { this.EnsureChildControlsAreCreatedSafe(); this.textBox.Timeout = value; } } /// /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. /// [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] public string AuthenticatedAsToolTip { get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } } /// /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) /// will be downloaded in order to provide a login split button. /// /// /// true to use a split button; otherwise, false to use a standard HTML button /// or a split button by downloading the YUI library yourself on the hosting web page. /// /// /// The split button brings in about 180KB of YUI javascript dependencies. /// [Bindable(true), DefaultValue(OpenIdAjaxTextBox.DownloadYahooUILibraryDefault), Category(BehaviorCategory)] [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] public bool DownloadYahooUILibrary { get { this.EnsureChildControlsAreCreatedSafe(); return this.textBox.DownloadYahooUILibrary; } set { this.EnsureChildControlsAreCreatedSafe(); this.textBox.DownloadYahooUILibrary = value; } } /// /// Gets the collection of buttons this selector should render to the browser. /// [PersistenceMode(PersistenceMode.InnerProperty)] public Collection Buttons { get { if (this.ViewState[ButtonsViewStateKey] == null) { var providers = new Collection(); this.ViewState[ButtonsViewStateKey] = providers; return providers; } else { return (Collection)this.ViewState[ButtonsViewStateKey]; } } } /// /// Gets a object that represents the child controls for a specified server control in the UI hierarchy. /// /// /// The collection of child controls for the specified server control. /// public override ControlCollection Controls { get { this.EnsureChildControls(); return base.Controls; } } /// /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). /// /// /// Usually a concatenation of the control's name and "_openidAuthData". /// protected override string OpenIdAuthDataFormKey { get { return this.UniqueID + AuthDataFormKeySuffix; } } /// /// Gets a value indicating whether some button in the selector will want /// to display the control. /// protected virtual bool OpenIdTextBoxVisible { get { return this.Buttons.OfType().Any(); } } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { foreach (var button in this.Buttons.OfType()) { button.Dispose(); } } base.Dispose(disposing); } /// /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. /// protected override void CreateChildControls() { this.EnsureChildControlsAreCreatedSafe(); base.CreateChildControls(); // Now do the ID specific work. this.EnsureID(); ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(this.UniqueID), "Control.EnsureID() failed to give us a unique ID. Try setting an ID on the OpenIdSelector control. But please also file this bug with the project owners."); this.Controls.Add(this.textBox); this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix; this.Controls.Add(this.positiveAssertionField); } /// /// Ensures that the child controls have been built, but doesn't set control /// properties that require executing in order to avoid /// certain initialization order problems. /// /// /// We don't just call EnsureChildControls() and then set the property on /// this.textBox itself because (apparently) setting this property in the ASPX /// page and thus calling this EnsureID() via EnsureChildControls() this early /// results in no ID. /// protected virtual void EnsureChildControlsAreCreatedSafe() { // If we've already created the child controls, this method is a no-op. if (this.textBox != null) { return; } this.textBox = new OpenIdAjaxTextBox(); this.textBox.ID = "openid_identifier"; this.textBox.HookFormSubmit = false; this.textBox.ShowLogOnPostBackButton = true; this.positiveAssertionField = new HiddenField(); } /// /// Raises the event. /// /// An object that contains the event data. protected override void OnInit(EventArgs e) { base.OnInit(e); // We force child control creation here so that they can get postback events. EnsureChildControls(); } /// /// Raises the event. /// /// An object that contains the event data. protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); this.EnsureValidButtons(); var css = new HtmlLink(); try { css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); css.Attributes["rel"] = "stylesheet"; css.Attributes["type"] = "text/css"; ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override } catch { css.Dispose(); throw; } // Import the .js file where most of the code is. this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName); // Provide javascript with a way to post the login assertion. const string PostLoginAssertionMethodName = "postLoginAssertion"; const string PositiveAssertionParameterName = "positiveAssertion"; const string ScriptFormat = @"window.{2} = function({0}) {{ $('#{3}')[0].setAttribute('value', {0}); {1}; }};"; string script = string.Format( CultureInfo.InvariantCulture, ScriptFormat, PositiveAssertionParameterName, this.Page.ClientScript.GetPostBackEventReference(this, null, false), PostLoginAssertionMethodName, this.positiveAssertionField.ClientID); this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Postback", script, true); this.PreloadDiscovery(this.Buttons.OfType().Select(op => op.OPIdentifier).Where(id => id != null)); this.textBox.Visible = this.OpenIdTextBoxVisible; } /// /// Sends server control content to a provided object, which writes the content to be rendered on the client. /// /// The object that receives the server control content. protected override void Render(HtmlTextWriter writer) { Contract.Assume(writer != null, "Missing contract"); writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); writer.RenderBeginTag(HtmlTextWriterTag.Ul); foreach (var button in this.Buttons) { button.RenderLeadingAttributes(writer); writer.RenderBeginTag(HtmlTextWriterTag.Li); writer.AddAttribute(HtmlTextWriterAttribute.Href, "#"); writer.RenderBeginTag(HtmlTextWriterTag.A); writer.RenderBeginTag(HtmlTextWriterTag.Div); writer.RenderBeginTag(HtmlTextWriterTag.Div); button.RenderButtonContent(writer, this); writer.RenderEndTag(); // writer.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); writer.RenderBeginTag(HtmlTextWriterTag.Div); writer.RenderEndTag(); writer.RenderEndTag(); // writer.RenderEndTag(); // writer.RenderEndTag(); // } writer.RenderEndTag(); // if (this.textBox.Visible) { writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none"); writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDForm"); writer.RenderBeginTag(HtmlTextWriterTag.Div); this.textBox.RenderControl(writer); writer.RenderEndTag(); // } this.positiveAssertionField.RenderControl(writer); } /// /// Ensures the collection has a valid set of buttons. /// private void EnsureValidButtons() { foreach (var button in this.Buttons) { button.EnsureValid(); } // Also make sure that there are appropriate numbers of each type of button. // TODO: code here } } }