//-----------------------------------------------------------------------
//
// 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\">{0}:OpenIdSelector>")]
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
}
}
}