//----------------------------------------------------------------------- // // 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); } } } }