//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/png")]
#pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use
namespace DotNetOpenAuth.OpenId.RelyingParty {
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Drawing.Design;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.Extensions.UI;
///
/// An ASP.NET control that provides a minimal text box that is OpenID-aware.
///
///
/// This control offers greater UI flexibility than the
/// control, but requires more work to be done by the hosting web site to
/// assemble a complete login experience.
///
[DefaultProperty("Text"), ValidationProperty("Text")]
[ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")]
public class OpenIdTextBox : OpenIdRelyingPartyControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler {
///
/// The name of the manifest stream containing the
/// OpenID logo that is placed inside the text box.
///
internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.png";
///
/// Default value for property.
///
protected const short TabIndexDefault = 0;
#region Property category constants
///
/// The "Simple Registration" category for properties.
///
private const string ProfileCategory = "Simple Registration";
#endregion
#region Property viewstate keys
///
/// The viewstate key to use for the property.
///
private const string RequestEmailViewStateKey = "RequestEmail";
///
/// The viewstate key to use for the property.
///
private const string RequestNicknameViewStateKey = "RequestNickname";
///
/// The viewstate key to use for the property.
///
private const string RequestPostalCodeViewStateKey = "RequestPostalCode";
///
/// The viewstate key to use for the property.
///
private const string RequestCountryViewStateKey = "RequestCountry";
///
/// The viewstate key to use for the property.
///
private const string RequestLanguageViewStateKey = "RequestLanguage";
///
/// The viewstate key to use for the property.
///
private const string RequestTimeZoneViewStateKey = "RequestTimeZone";
///
/// The viewstate key to use for the property.
///
private const string EnableRequestProfileViewStateKey = "EnableRequestProfile";
///
/// The viewstate key to use for the property.
///
private const string PolicyUrlViewStateKey = "PolicyUrl";
///
/// The viewstate key to use for the property.
///
private const string RequestFullNameViewStateKey = "RequestFullName";
///
/// The viewstate key to use for the property.
///
private const string PresetBorderViewStateKey = "PresetBorder";
///
/// The viewstate key to use for the property.
///
private const string ShowLogoViewStateKey = "ShowLogo";
///
/// The viewstate key to use for the property.
///
private const string RequestGenderViewStateKey = "RequestGender";
///
/// The viewstate key to use for the property.
///
private const string RequestBirthDateViewStateKey = "RequestBirthDate";
///
/// The viewstate key to use for the property.
///
private const string CssClassViewStateKey = "CssClass";
///
/// The viewstate key to use for the property.
///
private const string MaxLengthViewStateKey = "MaxLength";
///
/// The viewstate key to use for the property.
///
private const string ColumnsViewStateKey = "Columns";
///
/// The viewstate key to use for the property.
///
private const string TabIndexViewStateKey = "TabIndex";
///
/// The viewstate key to use for the property.
///
private const string EnabledViewStateKey = "Enabled";
///
/// The viewstate key to use for the property.
///
private const string NameViewStateKey = "Name";
///
/// The viewstate key to use for the property.
///
private const string TextViewStateKey = "Text";
#endregion
#region Property defaults
///
/// The default value for the property.
///
private const int ColumnsDefault = 40;
///
/// The default value for the property.
///
private const int MaxLengthDefault = 40;
///
/// The default value for the property.
///
private const string NameDefault = "openid_identifier";
///
/// The default value for the property.
///
private const bool EnableRequestProfileDefault = true;
///
/// The default value for the property.
///
private const bool ShowLogoDefault = true;
///
/// The default value for the property.
///
private const bool PresetBorderDefault = true;
///
/// The default value for the property.
///
private const string PolicyUrlDefault = "";
///
/// The default value for the property.
///
private const string CssClassDefault = "openid";
///
/// The default value for the property.
///
private const string TextDefault = "";
///
/// The default value for the property.
///
private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest;
///
/// The default value for the property.
///
private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest;
#endregion
///
/// An empty sreg request, used to compare with others to see if they too are empty.
///
private static readonly ClaimsRequest EmptyClaimsRequest = new ClaimsRequest();
///
/// Initializes a new instance of the class.
///
public OpenIdTextBox() {
}
#region IEditableTextControl Members
///
/// Occurs when the content of the text changes between posts to the server.
///
public event EventHandler TextChanged;
#endregion
#region Properties
///
/// Gets or sets the content of the text box.
///
[Bindable(true), DefaultValue(""), Category(AppearanceCategory)]
[Description("The content of the text box.")]
public string Text {
get {
return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty);
}
set {
// Try to store it as a validated identifier,
// but failing that at least store the text.
Identifier id;
if (Identifier.TryParse(value, out id)) {
this.Identifier = id;
} else {
// Be sure to set the viewstate AFTER setting the Identifier,
// since setting the Identifier clears the viewstate in OnIdentifierChanged.
this.Identifier = null;
this.ViewState[TextViewStateKey] = value;
}
}
}
///
/// Gets or sets the form name to use for this input field.
///
[Bindable(true), DefaultValue(NameDefault), Category(BehaviorCategory)]
[Description("The form name of this input field.")]
public string Name {
get { return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); }
set { this.ViewState[NameViewStateKey] = value; }
}
///
/// Gets or sets the CSS class assigned to the text box.
///
[Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)]
[Description("The CSS class assigned to the text box.")]
public string CssClass {
get { return (string)this.ViewState[CssClassViewStateKey]; }
set { this.ViewState[CssClassViewStateKey] = value; }
}
///
/// Gets or sets a value indicating whether to show the OpenID logo in the text box.
///
[Bindable(true), DefaultValue(ShowLogoDefault), Category(AppearanceCategory)]
[Description("The visibility of the OpenID logo in the text box.")]
public bool ShowLogo {
get { return (bool)(this.ViewState[ShowLogoViewStateKey] ?? ShowLogoDefault); }
set { this.ViewState[ShowLogoViewStateKey] = value; }
}
///
/// Gets or sets a value indicating whether to use inline styling to force a solid gray border.
///
[Bindable(true), DefaultValue(PresetBorderDefault), Category(AppearanceCategory)]
[Description("Whether to use inline styling to force a solid gray border.")]
public bool PresetBorder {
get { return (bool)(this.ViewState[PresetBorderViewStateKey] ?? PresetBorderDefault); }
set { this.ViewState[PresetBorderViewStateKey] = value; }
}
///
/// Gets or sets the width of the text box in characters.
///
[Bindable(true), DefaultValue(ColumnsDefault), Category(AppearanceCategory)]
[Description("The width of the text box in characters.")]
public int Columns {
get { return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); }
set { this.ViewState[ColumnsViewStateKey] = value; }
}
///
/// Gets or sets the maximum number of characters the browser should allow
///
[Bindable(true), DefaultValue(MaxLengthDefault), Category(AppearanceCategory)]
[Description("The maximum number of characters the browser should allow.")]
public int MaxLength {
get { return (int)(this.ViewState[MaxLengthViewStateKey] ?? MaxLengthDefault); }
set { this.ViewState[MaxLengthViewStateKey] = value; }
}
///
/// Gets or sets the tab index of the Web server control.
///
///
///
/// The tab index of the Web server control. The default is 0, which indicates that this property is not set.
///
///
/// The specified tab index is not between -32768 and 32767.
///
[Bindable(true), DefaultValue(TabIndexDefault), Category(BehaviorCategory)]
[Description("The tab index of the text box control.")]
public virtual short TabIndex {
get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); }
set { this.ViewState[TabIndexViewStateKey] = value; }
}
///
/// Gets or sets a value indicating whether this is enabled
/// in the browser for editing and will respond to incoming OpenID messages.
///
/// true if enabled; otherwise, false.
[Bindable(true), DefaultValue(true), Category(BehaviorCategory)]
[Description("Whether the control is editable in the browser and will respond to OpenID messages.")]
public bool Enabled {
get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); }
set { this.ViewState[EnabledViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's nickname from the Provider.
///
[Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's nickname from the Provider.")]
public DemandLevel RequestNickname {
get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); }
set { ViewState[RequestNicknameViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's email address from the Provider.
///
[Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's email address from the Provider.")]
public DemandLevel RequestEmail {
get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); }
set { ViewState[RequestEmailViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's full name from the Provider.
///
[Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's full name from the Provider")]
public DemandLevel RequestFullName {
get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); }
set { ViewState[RequestFullNameViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's birthdate from the Provider.
///
[Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's birthdate from the Provider.")]
public DemandLevel RequestBirthDate {
get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); }
set { ViewState[RequestBirthDateViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's gender from the Provider.
///
[Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's gender from the Provider.")]
public DemandLevel RequestGender {
get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); }
set { ViewState[RequestGenderViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's postal code from the Provider.
///
[Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's postal code from the Provider.")]
public DemandLevel RequestPostalCode {
get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); }
set { ViewState[RequestPostalCodeViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's country from the Provider.
///
[Bindable(true)]
[Category(ProfileCategory)]
[DefaultValue(RequestCountryDefault)]
[Description("Your level of interest in receiving the user's country from the Provider.")]
public DemandLevel RequestCountry {
get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); }
set { ViewState[RequestCountryViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's preferred language from the Provider.
///
[Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's preferred language from the Provider.")]
public DemandLevel RequestLanguage {
get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); }
set { ViewState[RequestLanguageViewStateKey] = value; }
}
///
/// Gets or sets your level of interest in receiving the user's time zone from the Provider.
///
[Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)]
[Description("Your level of interest in receiving the user's time zone from the Provider.")]
public DemandLevel RequestTimeZone {
get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); }
set { ViewState[RequestTimeZoneViewStateKey] = value; }
}
///
/// Gets or sets the URL to your privacy policy page that describes how
/// claims will be used and/or shared.
///
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
[Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)]
[Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")]
[UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
public string PolicyUrl {
get {
return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault;
}
set {
UriUtil.ValidateResolvableUrl(Page, DesignMode, value);
ViewState[PolicyUrlViewStateKey] = value;
}
}
///
/// Gets or sets a value indicating whether to use OpenID extensions
/// to retrieve profile data of the authenticating user.
///
[Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)]
[Description("Turns the entire Simple Registration extension on or off.")]
public bool EnableRequestProfile {
get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); }
set { ViewState[EnableRequestProfileViewStateKey] = value; }
}
#endregion
#region IPostBackDataHandler Members
///
/// When implemented by a class, processes postback data for an ASP.NET server control.
///
/// The key identifier for the control.
/// The collection of all incoming name values.
///
/// true if the server control's state changes as a result of the postback; otherwise, false.
///
bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
return this.LoadPostData(postDataKey, postCollection);
}
///
/// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed.
///
void IPostBackDataHandler.RaisePostDataChangedEvent() {
this.RaisePostDataChangedEvent();
}
#endregion
///
/// Creates the authentication requests for a given user-supplied Identifier.
///
/// The identifier to create a request for.
///
/// A sequence of authentication requests, any one of which may be
/// used to determine the user's control of the .
///
protected internal override IEnumerable CreateRequests(Identifier identifier) {
ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier");
// 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));
}
///
/// Checks for incoming OpenID authentication responses and fires appropriate events.
///
/// The object that contains the event data.
protected override void OnLoad(EventArgs e) {
if (!this.Enabled) {
return;
}
this.Page.RegisterRequiresPostBack(this);
base.OnLoad(e);
}
///
/// Called when the property is changed.
///
protected override void OnIdentifierChanged() {
this.ViewState.Remove(TextViewStateKey);
base.OnIdentifierChanged();
}
///
/// 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.");
if (this.ShowLogo) {
string logoUrl = Page.ClientScript.GetWebResourceUrl(
typeof(OpenIdTextBox), EmbeddedLogoResourceName);
writer.AddStyleAttribute(
HtmlTextWriterStyle.BackgroundImage,
string.Format(CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl)));
writer.AddStyleAttribute("background-repeat", "no-repeat");
writer.AddStyleAttribute("background-position", "0 50%");
writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px");
}
if (this.PresetBorder) {
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid");
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px");
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray");
}
if (!string.IsNullOrEmpty(this.CssClass)) {
writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass);
}
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
writer.AddAttribute(HtmlTextWriterAttribute.Name, HttpUtility.HtmlEncode(this.Name));
writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture));
writer.AddAttribute(HtmlTextWriterAttribute.Value, HttpUtility.HtmlEncode(this.Text));
writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.CurrentCulture));
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
}
///
/// When implemented by a class, processes postback data for an ASP.NET server control.
///
/// The key identifier for the control.
/// The collection of all incoming name values.
///
/// true if the server control's state changes as a result of the postback; otherwise, false.
///
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
Contract.Assume(postCollection != null, "Missing contract");
// If the control was temporarily hidden, it won't be in the Form data,
// and we'll just implicitly keep the last Text setting.
if (postCollection[this.Name] != null) {
this.Text = postCollection[this.Name];
return true;
}
return false;
}
///
/// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed.
///
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Preserve signature of interface we're implementing.")]
protected virtual void RaisePostDataChangedEvent() {
this.OnTextChanged();
}
///
/// Called on a postback when the Text property has changed.
///
protected virtual void OnTextChanged() {
EventHandler textChanged = this.TextChanged;
if (textChanged != null) {
textChanged(this, EventArgs.Empty);
}
}
///
/// Creates the authentication requests for a given user-supplied Identifier.
///
/// The authentication requests to prepare.
///
/// A sequence of authentication requests, any one of which may be
/// used to determine the user's control of the .
///
private IEnumerable CreateRequestsCore(IEnumerable requests) {
Contract.Requires(requests != null);
foreach (var request in requests) {
if (this.EnableRequestProfile) {
this.AddProfileArgs(request);
}
yield return request;
}
}
///
/// Adds extensions to a given authentication request to ask the Provider
/// for user profile data.
///
/// The authentication request to add the extensions to.
private void AddProfileArgs(IAuthenticationRequest request) {
Contract.Requires(request != null);
var sreg = new ClaimsRequest() {
Nickname = this.RequestNickname,
Email = this.RequestEmail,
FullName = this.RequestFullName,
BirthDate = this.RequestBirthDate,
Gender = this.RequestGender,
PostalCode = this.RequestPostalCode,
Country = this.RequestCountry,
Language = this.RequestLanguage,
TimeZone = this.RequestTimeZone,
PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ?
null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)),
};
// Only actually add the extension request if fields are actually being requested.
if (!sreg.Equals(EmptyClaimsRequest)) {
request.AddExtension(sreg);
}
}
}
}