FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / RelyingParty / OpenIdTextBox.cs
blob49471defba599f29744a12fc5f2e982dc7584bfe
1 //-----------------------------------------------------------------------
2 // <copyright file="OpenIdTextBox.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/gif")]
9 #pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use
11 namespace DotNetOpenAuth.OpenId.RelyingParty {
12 using System;
13 using System.Collections.Generic;
14 using System.Collections.Specialized;
15 using System.ComponentModel;
16 using System.Diagnostics;
17 using System.Diagnostics.CodeAnalysis;
18 using System.Globalization;
19 using System.Net;
20 using System.Text.RegularExpressions;
21 using System.Web;
22 using System.Web.Security;
23 using System.Web.UI;
24 using System.Web.UI.WebControls;
25 using DotNetOpenAuth.Configuration;
26 using DotNetOpenAuth.Messaging;
27 using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
29 /// <summary>
30 /// An ASP.NET control that provides a minimal text box that is OpenID-aware.
31 /// </summary>
32 /// <remarks>
33 /// This control offers greater UI flexibility than the <see cref="OpenIdLogin"/>
34 /// control, but requires more work to be done by the hosting web site to
35 /// assemble a complete login experience.
36 /// </remarks>
37 [DefaultProperty("Text"), ValidationProperty("Text")]
38 [ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")]
39 public class OpenIdTextBox : CompositeControl, IEditableTextControl, ITextControl {
40 /// <summary>
41 /// The name of the manifest stream containing the
42 /// OpenID logo that is placed inside the text box.
43 /// </summary>
44 internal const string EmbeddedLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.openid_login.gif";
46 /// <summary>
47 /// Default value for <see cref="TabIndex"/> property.
48 /// </summary>
49 protected const short TabIndexDefault = 0;
51 /// <summary>
52 /// Default value of <see cref="UsePersistentCookie"/>.
53 /// </summary>
54 protected const bool UsePersistentCookieDefault = false;
56 #region Property category constants
58 /// <summary>
59 /// The "Appearance" category for properties.
60 /// </summary>
61 private const string AppearanceCategory = "Appearance";
63 /// <summary>
64 /// The "Simple Registration" category for properties.
65 /// </summary>
66 private const string ProfileCategory = "Simple Registration";
68 /// <summary>
69 /// The "Behavior" category for properties.
70 /// </summary>
71 private const string BehaviorCategory = "Behavior";
73 #endregion
75 #region Property viewstate keys
77 /// <summary>
78 /// The viewstate key to use for the <see cref="RequestEmail"/> property.
79 /// </summary>
80 private const string RequestEmailViewStateKey = "RequestEmail";
82 /// <summary>
83 /// The viewstate key to use for the <see cref="RequestNickname"/> property.
84 /// </summary>
85 private const string RequestNicknameViewStateKey = "RequestNickname";
87 /// <summary>
88 /// The viewstate key to use for the <see cref="RequestPostalCode"/> property.
89 /// </summary>
90 private const string RequestPostalCodeViewStateKey = "RequestPostalCode";
92 /// <summary>
93 /// The viewstate key to use for the <see cref="RequestCountry"/> property.
94 /// </summary>
95 private const string RequestCountryViewStateKey = "RequestCountry";
97 /// <summary>
98 /// The viewstate key to use for the <see cref="RequireSsl"/> property.
99 /// </summary>
100 private const string RequireSslViewStateKey = "RequireSsl";
102 /// <summary>
103 /// The viewstate key to use for the <see cref="RequestLanguage"/> property.
104 /// </summary>
105 private const string RequestLanguageViewStateKey = "RequestLanguage";
107 /// <summary>
108 /// The viewstate key to use for the <see cref="RequestTimeZone"/> property.
109 /// </summary>
110 private const string RequestTimeZoneViewStateKey = "RequestTimeZone";
112 /// <summary>
113 /// The viewstate key to use for the <see cref="EnableRequestProfile"/> property.
114 /// </summary>
115 private const string EnableRequestProfileViewStateKey = "EnableRequestProfile";
117 /// <summary>
118 /// The viewstate key to use for the <see cref="PolicyUrl"/> property.
119 /// </summary>
120 private const string PolicyUrlViewStateKey = "PolicyUrl";
122 /// <summary>
123 /// The viewstate key to use for the <see cref="RequestFullName"/> property.
124 /// </summary>
125 private const string RequestFullNameViewStateKey = "RequestFullName";
127 /// <summary>
128 /// The viewstate key to use for the <see cref="PresetBorder"/> property.
129 /// </summary>
130 private const string PresetBorderViewStateKey = "PresetBorder";
132 /// <summary>
133 /// The viewstate key to use for the <see cref="ShowLogo"/> property.
134 /// </summary>
135 private const string ShowLogoViewStateKey = "ShowLogo";
137 /// <summary>
138 /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property.
139 /// </summary>
140 private const string UsePersistentCookieViewStateKey = "UsePersistentCookie";
142 /// <summary>
143 /// The viewstate key to use for the <see cref="RequestGender"/> property.
144 /// </summary>
145 private const string RequestGenderViewStateKey = "RequestGender";
147 /// <summary>
148 /// The viewstate key to use for the <see cref="ReturnToUrl"/> property.
149 /// </summary>
150 private const string ReturnToUrlViewStateKey = "ReturnToUrl";
152 /// <summary>
153 /// The viewstate key to use for the <see cref="Stateless"/> property.
154 /// </summary>
155 private const string StatelessViewStateKey = "Stateless";
157 /// <summary>
158 /// The viewstate key to use for the <see cref="ImmediateMode"/> property.
159 /// </summary>
160 private const string ImmediateModeViewStateKey = "ImmediateMode";
162 /// <summary>
163 /// The viewstate key to use for the <see cref="RequestBirthDate"/> property.
164 /// </summary>
165 private const string RequestBirthDateViewStateKey = "RequestBirthDate";
167 /// <summary>
168 /// The viewstate key to use for the <see cref="RealmUrl"/> property.
169 /// </summary>
170 private const string RealmUrlViewStateKey = "RealmUrl";
172 #endregion
174 #region Property defaults
176 /// <summary>
177 /// The default value for the <see cref="Columns"/> property.
178 /// </summary>
179 private const int ColumnsDefault = 40;
181 /// <summary>
182 /// The default value for the <see cref="MaxLength"/> property.
183 /// </summary>
184 private const int MaxLengthDefault = 40;
186 /// <summary>
187 /// The default value for the <see cref="EnableRequestProfile"/> property.
188 /// </summary>
189 private const bool EnableRequestProfileDefault = true;
191 /// <summary>
192 /// The default value for the <see cref="RequireSsl"/> property.
193 /// </summary>
194 private const bool RequireSslDefault = false;
196 /// <summary>
197 /// The default value for the <see cref="ImmediateMode"/> property.
198 /// </summary>
199 private const bool ImmediateModeDefault = false;
201 /// <summary>
202 /// The default value for the <see cref="Stateless"/> property.
203 /// </summary>
204 private const bool StatelessDefault = false;
206 /// <summary>
207 /// The default value for the <see cref="ShowLogo"/> property.
208 /// </summary>
209 private const bool ShowLogoDefault = true;
211 /// <summary>
212 /// The default value for the <see cref="PresetBorder"/> property.
213 /// </summary>
214 private const bool PresetBorderDefault = true;
216 /// <summary>
217 /// The default value for the <see cref="PolicyUrl"/> property.
218 /// </summary>
219 private const string PolicyUrlDefault = "";
221 /// <summary>
222 /// The default value for the <see cref="CssClass"/> property.
223 /// </summary>
224 private const string CssClassDefault = "openid";
226 /// <summary>
227 /// The default value for the <see cref="ReturnToUrl"/> property.
228 /// </summary>
229 private const string ReturnToUrlDefault = "";
231 /// <summary>
232 /// The default value for the <see cref="Text"/> property.
233 /// </summary>
234 private const string TextDefault = "";
236 /// <summary>
237 /// The default value for the <see cref="RealmUrl"/> property.
238 /// </summary>
239 private const string RealmUrlDefault = "~/";
241 /// <summary>
242 /// The default value for the <see cref="RequestEmail"/> property.
243 /// </summary>
244 private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest;
246 /// <summary>
247 /// The default value for the <see cref="RequestPostalCode"/> property.
248 /// </summary>
249 private const DemandLevel RequestPostalCodeDefault = DemandLevel.NoRequest;
251 /// <summary>
252 /// The default value for the <see cref="RequestCountry"/> property.
253 /// </summary>
254 private const DemandLevel RequestCountryDefault = DemandLevel.NoRequest;
256 /// <summary>
257 /// The default value for the <see cref="RequestLanguage"/> property.
258 /// </summary>
259 private const DemandLevel RequestLanguageDefault = DemandLevel.NoRequest;
261 /// <summary>
262 /// The default value for the <see cref="RequestTimeZone"/> property.
263 /// </summary>
264 private const DemandLevel RequestTimeZoneDefault = DemandLevel.NoRequest;
266 /// <summary>
267 /// The default value for the <see cref="RequestNickname"/> property.
268 /// </summary>
269 private const DemandLevel RequestNicknameDefault = DemandLevel.NoRequest;
271 /// <summary>
272 /// The default value for the <see cref="RequestFullName"/> property.
273 /// </summary>
274 private const DemandLevel RequestFullNameDefault = DemandLevel.NoRequest;
276 /// <summary>
277 /// The default value for the <see cref="RequestBirthDate"/> property.
278 /// </summary>
279 private const DemandLevel RequestBirthDateDefault = DemandLevel.NoRequest;
281 /// <summary>
282 /// The default value for the <see cref="RequestGender"/> property.
283 /// </summary>
284 private const DemandLevel RequestGenderDefault = DemandLevel.NoRequest;
286 #endregion
288 /// <summary>
289 /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property.
290 /// </summary>
291 private const string UsePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie";
293 /// <summary>
294 /// The text in the text box before the text box is instantiated.
295 /// </summary>
296 private string text = TextDefault;
298 /// <summary>
299 /// The text box itself.
300 /// </summary>
301 private TextBox wrappedTextBox;
303 /// <summary>
304 /// Initializes a new instance of the <see cref="OpenIdTextBox"/> class.
305 /// </summary>
306 public OpenIdTextBox() {
307 this.InitializeControls();
310 #region Events
312 /// <summary>
313 /// Fired upon completion of a successful login.
314 /// </summary>
315 [Description("Fired upon completion of a successful login.")]
316 public event EventHandler<OpenIdEventArgs> LoggedIn;
318 /// <summary>
319 /// Fired when a login attempt fails.
320 /// </summary>
321 [Description("Fired when a login attempt fails.")]
322 public event EventHandler<OpenIdEventArgs> Failed;
324 /// <summary>
325 /// Fired when an authentication attempt is canceled at the OpenID Provider.
326 /// </summary>
327 [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")]
328 public event EventHandler<OpenIdEventArgs> Canceled;
330 /// <summary>
331 /// Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.
332 /// </summary>
333 [Description("Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.")]
334 public event EventHandler<OpenIdEventArgs> SetupRequired;
336 #endregion
337 #region IEditableTextControl Members
339 /// <summary>
340 /// Occurs when the content of the text changes between posts to the server.
341 /// </summary>
342 public event EventHandler TextChanged {
343 add { this.WrappedTextBox.TextChanged += value; }
344 remove { this.WrappedTextBox.TextChanged -= value; }
347 #endregion
349 #region Properties
351 /// <summary>
352 /// Gets or sets the content of the text box.
353 /// </summary>
354 [Bindable(true), DefaultValue(""), Category(AppearanceCategory)]
355 [Description("The content of the text box.")]
356 public string Text {
357 get {
358 return this.WrappedTextBox != null ? this.WrappedTextBox.Text : this.text;
361 set {
362 this.text = value;
363 if (this.WrappedTextBox != null) {
364 this.WrappedTextBox.Text = value;
369 /// <summary>
370 /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site.
371 /// </summary>
372 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
373 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")]
374 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
375 [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)]
376 [Description("The OpenID Realm of the relying party web site.")]
377 public string RealmUrl {
378 get {
379 return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault);
382 set {
383 if (Page != null && !DesignMode) {
384 // Validate new value by trying to construct a Realm object based on it.
385 new Realm(OpenIdUtilities.GetResolvedRealm(Page, value)); // throws an exception on failure.
386 } else {
387 // We can't fully test it, but it should start with either ~/ or a protocol.
388 if (Regex.IsMatch(value, @"^https?://")) {
389 new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards
390 } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
391 // this is valid too
392 } else {
393 throw new UriFormatException();
396 ViewState[RealmUrlViewStateKey] = value;
400 /// <summary>
401 /// Gets or sets the OpenID ReturnTo of the relying party web site.
402 /// </summary>
403 [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
404 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
405 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
406 [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)]
407 [Description("The OpenID ReturnTo of the relying party web site.")]
408 public string ReturnToUrl {
409 get {
410 return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault);
413 set {
414 if (this.Page != null && !this.DesignMode) {
415 // Validate new value by trying to construct a Uri based on it.
416 new Uri(MessagingUtilities.GetRequestUrlFromContext(), this.Page.ResolveUrl(value)); // throws an exception on failure.
417 } else {
418 // We can't fully test it, but it should start with either ~/ or a protocol.
419 if (Regex.IsMatch(value, @"^https?://")) {
420 new Uri(value); // make sure it's fully-qualified, but ignore wildcards
421 } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
422 // this is valid too
423 } else {
424 throw new UriFormatException();
428 this.ViewState[ReturnToUrlViewStateKey] = value;
432 /// <summary>
433 /// Gets or sets a value indicating whether to use immediate mode in the
434 /// OpenID protocol.
435 /// </summary>
436 /// <value>
437 /// True if a Provider should reply immediately to the authentication request
438 /// without interacting with the user. False if the Provider can take time
439 /// to authenticate the user in order to complete an authentication attempt.
440 /// </value>
441 /// <remarks>
442 /// Setting this to true is sometimes useful in AJAX scenarios. Setting this to
443 /// true can cause failed authentications when the user truly controls an
444 /// Identifier, but must complete an authentication step with the Provider before
445 /// the Provider will approve the login from this relying party.
446 /// </remarks>
447 [Bindable(true), DefaultValue(ImmediateModeDefault), Category(BehaviorCategory)]
448 [Description("Whether the Provider should respond immediately to an authentication attempt without interacting with the user.")]
449 public bool ImmediateMode {
450 get { return (bool)(ViewState[ImmediateModeViewStateKey] ?? ImmediateModeDefault); }
451 set { ViewState[ImmediateModeViewStateKey] = value; }
454 /// <summary>
455 /// Gets or sets a value indicating whether stateless mode is used.
456 /// </summary>
457 [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)]
458 [Description("Controls whether stateless mode is used.")]
459 public bool Stateless {
460 get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); }
461 set { ViewState[StatelessViewStateKey] = value; }
464 /// <summary>
465 /// Gets or sets the CSS class assigned to the text box.
466 /// </summary>
467 [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)]
468 [Description("The CSS class assigned to the text box.")]
469 public override string CssClass {
470 get { return this.WrappedTextBox.CssClass; }
471 set { this.WrappedTextBox.CssClass = value; }
474 /// <summary>
475 /// Gets or sets a value indicating whether to show the OpenID logo in the text box.
476 /// </summary>
477 [Bindable(true), DefaultValue(ShowLogoDefault), Category(AppearanceCategory)]
478 [Description("The visibility of the OpenID logo in the text box.")]
479 public bool ShowLogo {
480 get { return (bool)(this.ViewState[ShowLogoViewStateKey] ?? ShowLogoDefault); }
481 set { this.ViewState[ShowLogoViewStateKey] = value; }
484 /// <summary>
485 /// Gets or sets a value indicating whether to use inline styling to force a solid gray border.
486 /// </summary>
487 [Bindable(true), DefaultValue(PresetBorderDefault), Category(AppearanceCategory)]
488 [Description("Whether to use inline styling to force a solid gray border.")]
489 public bool PresetBorder {
490 get { return (bool)(this.ViewState[PresetBorderViewStateKey] ?? PresetBorderDefault); }
491 set { this.ViewState[PresetBorderViewStateKey] = value; }
494 /// <summary>
495 /// Gets or sets a value indicating whether to send a persistent cookie upon successful
496 /// login so the user does not have to log in upon returning to this site.
497 /// </summary>
498 [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)]
499 [Description("Whether to send a persistent cookie upon successful " +
500 "login so the user does not have to log in upon returning to this site.")]
501 public virtual bool UsePersistentCookie {
502 get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); }
503 set { this.ViewState[UsePersistentCookieViewStateKey] = value; }
506 /// <summary>
507 /// Gets or sets the width of the text box in characters.
508 /// </summary>
509 [Bindable(true), DefaultValue(ColumnsDefault), Category(AppearanceCategory)]
510 [Description("The width of the text box in characters.")]
511 public int Columns {
512 get { return this.WrappedTextBox.Columns; }
513 set { this.WrappedTextBox.Columns = value; }
516 /// <summary>
517 /// Gets or sets the maximum number of characters the browser should allow
518 /// </summary>
519 [Bindable(true), DefaultValue(MaxLengthDefault), Category(AppearanceCategory)]
520 [Description("The maximum number of characters the browser should allow.")]
521 public int MaxLength {
522 get { return this.WrappedTextBox.MaxLength; }
523 set { this.WrappedTextBox.MaxLength = value; }
526 /// <summary>
527 /// Gets or sets the tab index of the Web server control.
528 /// </summary>
529 /// <value></value>
530 /// <returns>
531 /// The tab index of the Web server control. The default is 0, which indicates that this property is not set.
532 /// </returns>
533 /// <exception cref="T:System.ArgumentOutOfRangeException">
534 /// The specified tab index is not between -32768 and 32767.
535 /// </exception>
536 [Bindable(true), DefaultValue(TabIndexDefault), Category(BehaviorCategory)]
537 [Description("The tab index of the text box control.")]
538 public override short TabIndex {
539 get { return this.WrappedTextBox.TabIndex; }
540 set { this.WrappedTextBox.TabIndex = value; }
543 /// <summary>
544 /// Gets or sets your level of interest in receiving the user's nickname from the Provider.
545 /// </summary>
546 [Bindable(true), DefaultValue(RequestNicknameDefault), Category(ProfileCategory)]
547 [Description("Your level of interest in receiving the user's nickname from the Provider.")]
548 public DemandLevel RequestNickname {
549 get { return (DemandLevel)(ViewState[RequestNicknameViewStateKey] ?? RequestNicknameDefault); }
550 set { ViewState[RequestNicknameViewStateKey] = value; }
553 /// <summary>
554 /// Gets or sets your level of interest in receiving the user's email address from the Provider.
555 /// </summary>
556 [Bindable(true), DefaultValue(RequestEmailDefault), Category(ProfileCategory)]
557 [Description("Your level of interest in receiving the user's email address from the Provider.")]
558 public DemandLevel RequestEmail {
559 get { return (DemandLevel)(ViewState[RequestEmailViewStateKey] ?? RequestEmailDefault); }
560 set { ViewState[RequestEmailViewStateKey] = value; }
563 /// <summary>
564 /// Gets or sets your level of interest in receiving the user's full name from the Provider.
565 /// </summary>
566 [Bindable(true), DefaultValue(RequestFullNameDefault), Category(ProfileCategory)]
567 [Description("Your level of interest in receiving the user's full name from the Provider")]
568 public DemandLevel RequestFullName {
569 get { return (DemandLevel)(ViewState[RequestFullNameViewStateKey] ?? RequestFullNameDefault); }
570 set { ViewState[RequestFullNameViewStateKey] = value; }
573 /// <summary>
574 /// Gets or sets your level of interest in receiving the user's birthdate from the Provider.
575 /// </summary>
576 [Bindable(true), DefaultValue(RequestBirthDateDefault), Category(ProfileCategory)]
577 [Description("Your level of interest in receiving the user's birthdate from the Provider.")]
578 public DemandLevel RequestBirthDate {
579 get { return (DemandLevel)(ViewState[RequestBirthDateViewStateKey] ?? RequestBirthDateDefault); }
580 set { ViewState[RequestBirthDateViewStateKey] = value; }
583 /// <summary>
584 /// Gets or sets your level of interest in receiving the user's gender from the Provider.
585 /// </summary>
586 [Bindable(true), DefaultValue(RequestGenderDefault), Category(ProfileCategory)]
587 [Description("Your level of interest in receiving the user's gender from the Provider.")]
588 public DemandLevel RequestGender {
589 get { return (DemandLevel)(ViewState[RequestGenderViewStateKey] ?? RequestGenderDefault); }
590 set { ViewState[RequestGenderViewStateKey] = value; }
593 /// <summary>
594 /// Gets or sets your level of interest in receiving the user's postal code from the Provider.
595 /// </summary>
596 [Bindable(true), DefaultValue(RequestPostalCodeDefault), Category(ProfileCategory)]
597 [Description("Your level of interest in receiving the user's postal code from the Provider.")]
598 public DemandLevel RequestPostalCode {
599 get { return (DemandLevel)(ViewState[RequestPostalCodeViewStateKey] ?? RequestPostalCodeDefault); }
600 set { ViewState[RequestPostalCodeViewStateKey] = value; }
603 /// <summary>
604 /// Gets or sets your level of interest in receiving the user's country from the Provider.
605 /// </summary>
606 [Bindable(true)]
607 [Category(ProfileCategory)]
608 [DefaultValue(RequestCountryDefault)]
609 [Description("Your level of interest in receiving the user's country from the Provider.")]
610 public DemandLevel RequestCountry {
611 get { return (DemandLevel)(ViewState[RequestCountryViewStateKey] ?? RequestCountryDefault); }
612 set { ViewState[RequestCountryViewStateKey] = value; }
615 /// <summary>
616 /// Gets or sets your level of interest in receiving the user's preferred language from the Provider.
617 /// </summary>
618 [Bindable(true), DefaultValue(RequestLanguageDefault), Category(ProfileCategory)]
619 [Description("Your level of interest in receiving the user's preferred language from the Provider.")]
620 public DemandLevel RequestLanguage {
621 get { return (DemandLevel)(ViewState[RequestLanguageViewStateKey] ?? RequestLanguageDefault); }
622 set { ViewState[RequestLanguageViewStateKey] = value; }
625 /// <summary>
626 /// Gets or sets your level of interest in receiving the user's time zone from the Provider.
627 /// </summary>
628 [Bindable(true), DefaultValue(RequestTimeZoneDefault), Category(ProfileCategory)]
629 [Description("Your level of interest in receiving the user's time zone from the Provider.")]
630 public DemandLevel RequestTimeZone {
631 get { return (DemandLevel)(ViewState[RequestTimeZoneViewStateKey] ?? RequestTimeZoneDefault); }
632 set { ViewState[RequestTimeZoneViewStateKey] = value; }
635 /// <summary>
636 /// Gets or sets the URL to your privacy policy page that describes how
637 /// claims will be used and/or shared.
638 /// </summary>
639 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")]
640 [Bindable(true), DefaultValue(PolicyUrlDefault), Category(ProfileCategory)]
641 [Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")]
642 public string PolicyUrl {
643 get {
644 return (string)ViewState[PolicyUrlViewStateKey] ?? PolicyUrlDefault;
647 set {
648 UriUtil.ValidateResolvableUrl(Page, DesignMode, value);
649 ViewState[PolicyUrlViewStateKey] = value;
653 /// <summary>
654 /// Gets or sets a value indicating whether to use OpenID extensions
655 /// to retrieve profile data of the authenticating user.
656 /// </summary>
657 [Bindable(true), DefaultValue(EnableRequestProfileDefault), Category(ProfileCategory)]
658 [Description("Turns the entire Simple Registration extension on or off.")]
659 public bool EnableRequestProfile {
660 get { return (bool)(ViewState[EnableRequestProfileViewStateKey] ?? EnableRequestProfileDefault); }
661 set { ViewState[EnableRequestProfileViewStateKey] = value; }
664 /// <summary>
665 /// Gets or sets a value indicating whether to enforce on high security mode,
666 /// which requires the full authentication pipeline to be protected by SSL.
667 /// </summary>
668 [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)]
669 [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")]
670 public bool RequireSsl {
671 get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); }
672 set { ViewState[RequireSslViewStateKey] = value; }
675 /// <summary>
676 /// Gets or sets the type of the custom application store to use, or <c>null</c> to use the default.
677 /// </summary>
678 /// <remarks>
679 /// If set, this property must be set in each Page Load event
680 /// as it is not persisted across postbacks.
681 /// </remarks>
682 public IRelyingPartyApplicationStore CustomApplicationStore { get; set; }
684 #endregion
686 #region Properties to hide
688 /// <summary>
689 /// Gets or sets the foreground color (typically the color of the text) of the Web server control.
690 /// </summary>
691 /// <returns>
692 /// A <see cref="T:System.Drawing.Color"/> that represents the foreground color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>.
693 /// </returns>
694 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
695 public override System.Drawing.Color ForeColor {
696 get { throw new NotSupportedException(); }
697 set { throw new NotSupportedException(); }
700 /// <summary>
701 /// Gets or sets the background color of the Web server control.
702 /// </summary>
703 /// <returns>
704 /// A <see cref="T:System.Drawing.Color"/> that represents the background color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set.
705 /// </returns>
706 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
707 public override System.Drawing.Color BackColor {
708 get { throw new NotSupportedException(); }
709 set { throw new NotSupportedException(); }
712 /// <summary>
713 /// Gets or sets the border color of the Web control.
714 /// </summary>
715 /// <returns>
716 /// A <see cref="T:System.Drawing.Color"/> that represents the border color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set.
717 /// </returns>
718 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
719 public override System.Drawing.Color BorderColor {
720 get { throw new NotSupportedException(); }
721 set { throw new NotSupportedException(); }
724 /// <summary>
725 /// Gets or sets the border width of the Web server control.
726 /// </summary>
727 /// <returns>
728 /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the border width of a Web server control. The default value is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>, which indicates that this property is not set.
729 /// </returns>
730 /// <exception cref="T:System.ArgumentException">
731 /// The specified border width is a negative value.
732 /// </exception>
733 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
734 public override Unit BorderWidth {
735 get { return Unit.Empty; }
736 set { throw new NotSupportedException(); }
739 /// <summary>
740 /// Gets or sets the border style of the Web server control.
741 /// </summary>
742 /// <returns>
743 /// One of the <see cref="T:System.Web.UI.WebControls.BorderStyle"/> enumeration values. The default is NotSet.
744 /// </returns>
745 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
746 public override BorderStyle BorderStyle {
747 get { return BorderStyle.None; }
748 set { throw new NotSupportedException(); }
751 /// <summary>
752 /// Gets the font properties associated with the Web server control.
753 /// </summary>
754 /// <returns>
755 /// A <see cref="T:System.Web.UI.WebControls.FontInfo"/> that represents the font properties of the Web server control.
756 /// </returns>
757 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
758 public override FontInfo Font {
759 get { return null; }
762 /// <summary>
763 /// Gets or sets the height of the Web server control.
764 /// </summary>
765 /// <returns>
766 /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the height of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>.
767 /// </returns>
768 /// <exception cref="T:System.ArgumentException">
769 /// The height was set to a negative value.
770 /// </exception>
771 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
772 public override Unit Height {
773 get { return Unit.Empty; }
774 set { throw new NotSupportedException(); }
777 /// <summary>
778 /// Gets or sets the width of the Web server control.
779 /// </summary>
780 /// <returns>
781 /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the width of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>.
782 /// </returns>
783 /// <exception cref="T:System.ArgumentException">
784 /// The width of the Web server control was set to a negative value.
785 /// </exception>
786 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
787 public override Unit Width {
788 get { return Unit.Empty; }
789 set { throw new NotSupportedException(); }
792 /// <summary>
793 /// Gets or sets the text displayed when the mouse pointer hovers over the Web server control.
794 /// </summary>
795 /// <returns>
796 /// The text displayed when the mouse pointer hovers over the Web server control. The default is <see cref="F:System.String.Empty"/>.
797 /// </returns>
798 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
799 public override string ToolTip {
800 get { return string.Empty; }
801 set { throw new NotSupportedException(); }
804 /// <summary>
805 /// Gets or sets the skin to apply to the control.
806 /// </summary>
807 /// <returns>
808 /// The name of the skin to apply to the control. The default is <see cref="F:System.String.Empty"/>.
809 /// </returns>
810 /// <exception cref="T:System.ArgumentException">
811 /// The skin specified in the <see cref="P:System.Web.UI.WebControls.WebControl.SkinID"/> property does not exist in the theme.
812 /// </exception>
813 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
814 public override string SkinID {
815 get { return string.Empty; }
816 set { throw new NotSupportedException(); }
819 /// <summary>
820 /// Gets or sets a value indicating whether themes apply to this control.
821 /// </summary>
822 /// <returns>true to use themes; otherwise, false. The default is false.
823 /// </returns>
824 [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)]
825 public override bool EnableTheming {
826 get { return false; }
827 set { throw new NotSupportedException(); }
830 #endregion
832 /// <summary>
833 /// Gets the <see cref="TextBox"/> control that this control wraps.
834 /// </summary>
835 protected TextBox WrappedTextBox {
836 get { return this.wrappedTextBox; }
839 /// <summary>
840 /// Gets or sets a value indicating whether the text box should
841 /// receive input focus when the web page appears.
842 /// </summary>
843 protected bool ShouldBeFocused { get; set; }
845 /// <summary>
846 /// Gets or sets the OpenID authentication request that is about to be sent.
847 /// </summary>
848 protected IAuthenticationRequest Request { get; set; }
850 /// <summary>
851 /// Sets the input focus to start on the text box when the page appears
852 /// in the user's browser.
853 /// </summary>
854 public override void Focus() {
855 if (Controls.Count == 0) {
856 this.ShouldBeFocused = true;
857 } else {
858 this.WrappedTextBox.Focus();
862 /// <summary>
863 /// Constructs the authentication request and returns it.
864 /// </summary>
865 /// <returns>The instantiated authentication request.</returns>
866 /// <remarks>
867 /// <para>This method need not be called before calling the <see cref="LogOn"/> method,
868 /// but is offered in the event that adding extensions to the request is desired.</para>
869 /// <para>The Simple Registration extension arguments are added to the request
870 /// before returning if <see cref="EnableRequestProfile"/> is set to true.</para>
871 /// </remarks>
872 public IAuthenticationRequest CreateRequest() {
873 ErrorUtilities.VerifyOperation(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled);
874 ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty);
876 try {
877 var consumer = this.CreateRelyingParty();
879 // Resolve the trust root, and swap out the scheme and port if necessary to match the
880 // return_to URL, since this match is required by OpenId, and the consumer app
881 // may be using HTTP at some times and HTTPS at others.
882 UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl);
883 realm.Scheme = Page.Request.Url.Scheme;
884 realm.Port = Page.Request.Url.Port;
886 // Initiate openid request
887 // We use TryParse here to avoid throwing an exception which
888 // might slip through our validator control if it is disabled.
889 Identifier userSuppliedIdentifier;
890 if (Identifier.TryParse(this.Text, out userSuppliedIdentifier)) {
891 Realm typedRealm = new Realm(realm);
892 if (string.IsNullOrEmpty(this.ReturnToUrl)) {
893 this.Request = consumer.CreateRequest(userSuppliedIdentifier, typedRealm);
894 } else {
895 Uri returnTo = new Uri(MessagingUtilities.GetRequestUrlFromContext(), this.ReturnToUrl);
896 this.Request = consumer.CreateRequest(userSuppliedIdentifier, typedRealm, returnTo);
898 this.Request.Mode = this.ImmediateMode ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup;
899 if (this.EnableRequestProfile) {
900 this.AddProfileArgs(this.Request);
903 // Add state that needs to survive across the redirect.
904 this.Request.AddCallbackArguments(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture));
905 } else {
906 Logger.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", this.Text);
907 this.Request = null;
909 } catch (ProtocolException ex) {
910 this.OnFailed(new FailedAuthenticationResponse(ex));
913 return this.Request;
916 /// <summary>
917 /// Immediately redirects to the OpenID Provider to verify the Identifier
918 /// provided in the text box.
919 /// </summary>
920 public void LogOn() {
921 if (this.Request == null) {
922 this.CreateRequest(); // sets this.Request
925 if (this.Request != null) {
926 this.Request.RedirectToProvider();
930 /// <summary>
931 /// Creates the text box control.
932 /// </summary>
933 protected override void CreateChildControls() {
934 base.CreateChildControls();
936 this.Controls.Add(this.wrappedTextBox);
937 if (this.ShouldBeFocused) {
938 this.WrappedTextBox.Focus();
942 /// <summary>
943 /// Initializes the text box control.
944 /// </summary>
945 protected virtual void InitializeControls() {
946 this.wrappedTextBox = new TextBox();
947 this.wrappedTextBox.ID = "wrappedTextBox";
948 this.wrappedTextBox.CssClass = CssClassDefault;
949 this.wrappedTextBox.Columns = ColumnsDefault;
950 this.wrappedTextBox.Text = this.text;
951 this.wrappedTextBox.TabIndex = TabIndexDefault;
954 /// <summary>
955 /// Checks for incoming OpenID authentication responses and fires appropriate events.
956 /// </summary>
957 /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
958 protected override void OnLoad(EventArgs e) {
959 base.OnLoad(e);
961 if (!Enabled || Page.IsPostBack) {
962 return;
965 var rp = this.CreateRelyingParty();
966 var response = rp.GetResponse();
967 if (response != null) {
968 string persistentString = response.GetCallbackArgument(UsePersistentCookieCallbackKey);
969 bool persistentBool;
970 if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) {
971 this.UsePersistentCookie = persistentBool;
974 switch (response.Status) {
975 case AuthenticationStatus.Canceled:
976 this.OnCanceled(response);
977 break;
978 case AuthenticationStatus.Authenticated:
979 this.OnLoggedIn(response);
980 break;
981 case AuthenticationStatus.SetupRequired:
982 this.OnSetupRequired(response);
983 break;
984 case AuthenticationStatus.Failed:
985 this.OnFailed(response);
986 break;
987 default:
988 throw new InvalidOperationException("Unexpected response status code.");
993 /// <summary>
994 /// Prepares the text box to be rendered.
995 /// </summary>
996 /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
997 protected override void OnPreRender(EventArgs e) {
998 base.OnPreRender(e);
1000 if (this.ShowLogo) {
1001 string logoUrl = Page.ClientScript.GetWebResourceUrl(
1002 typeof(OpenIdTextBox), EmbeddedLogoResourceName);
1003 this.WrappedTextBox.Style[HtmlTextWriterStyle.BackgroundImage] = string.Format(
1004 CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl));
1005 this.WrappedTextBox.Style["background-repeat"] = "no-repeat";
1006 this.WrappedTextBox.Style["background-position"] = "0 50%";
1007 this.WrappedTextBox.Style[HtmlTextWriterStyle.PaddingLeft] = "18px";
1010 if (this.PresetBorder) {
1011 this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderStyle] = "solid";
1012 this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderWidth] = "1px";
1013 this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderColor] = "lightgray";
1017 #region Events
1019 /// <summary>
1020 /// Fires the <see cref="LoggedIn"/> event.
1021 /// </summary>
1022 /// <param name="response">The response.</param>
1023 protected virtual void OnLoggedIn(IAuthenticationResponse response) {
1024 ErrorUtilities.VerifyArgumentNotNull(response, "response");
1025 ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response.");
1027 var loggedIn = this.LoggedIn;
1028 OpenIdEventArgs args = new OpenIdEventArgs(response);
1029 if (loggedIn != null) {
1030 loggedIn(this, args);
1033 if (!args.Cancel) {
1034 FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie);
1038 /// <summary>
1039 /// Fires the <see cref="Failed"/> event.
1040 /// </summary>
1041 /// <param name="response">The response.</param>
1042 protected virtual void OnFailed(IAuthenticationResponse response) {
1043 ErrorUtilities.VerifyArgumentNotNull(response, "response");
1044 ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type.");
1046 var failed = this.Failed;
1047 if (failed != null) {
1048 failed(this, new OpenIdEventArgs(response));
1052 /// <summary>
1053 /// Fires the <see cref="Canceled"/> event.
1054 /// </summary>
1055 /// <param name="response">The response.</param>
1056 protected virtual void OnCanceled(IAuthenticationResponse response) {
1057 ErrorUtilities.VerifyArgumentNotNull(response, "response");
1058 ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type.");
1060 var canceled = this.Canceled;
1061 if (canceled != null) {
1062 canceled(this, new OpenIdEventArgs(response));
1066 /// <summary>
1067 /// Fires the <see cref="SetupRequired"/> event.
1068 /// </summary>
1069 /// <param name="response">The response.</param>
1070 protected virtual void OnSetupRequired(IAuthenticationResponse response) {
1071 ErrorUtilities.VerifyArgumentNotNull(response, "response");
1072 ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.SetupRequired, "Firing SetupRequired event for the wrong response type.");
1074 // Why are we firing Failed when we're OnSetupRequired? Backward compatibility.
1075 var setupRequired = this.SetupRequired;
1076 if (setupRequired != null) {
1077 setupRequired(this, new OpenIdEventArgs(response));
1081 #endregion
1083 /// <summary>
1084 /// Adds extensions to a given authentication request to ask the Provider
1085 /// for user profile data.
1086 /// </summary>
1087 /// <param name="request">The authentication request to add the extensions to.</param>
1088 private void AddProfileArgs(IAuthenticationRequest request) {
1089 ErrorUtilities.VerifyArgumentNotNull(request, "request");
1091 request.AddExtension(new ClaimsRequest() {
1092 Nickname = this.RequestNickname,
1093 Email = this.RequestEmail,
1094 FullName = this.RequestFullName,
1095 BirthDate = this.RequestBirthDate,
1096 Gender = this.RequestGender,
1097 PostalCode = this.RequestPostalCode,
1098 Country = this.RequestCountry,
1099 Language = this.RequestLanguage,
1100 TimeZone = this.RequestTimeZone,
1101 PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ?
1102 null : new Uri(MessagingUtilities.GetRequestUrlFromContext(), this.Page.ResolveUrl(this.PolicyUrl)),
1106 /// <summary>
1107 /// Creates the relying party instance used to generate authentication requests.
1108 /// </summary>
1109 /// <returns>The instantiated relying party.</returns>
1110 private OpenIdRelyingParty CreateRelyingParty() {
1111 // If we're in stateful mode, first use the explicitly given one on this control if there
1112 // is one. Then try the configuration file specified one. Finally, use the default
1113 // in-memory one that's built into OpenIdRelyingParty.
1114 IRelyingPartyApplicationStore store = this.Stateless ? null :
1115 (this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore));
1116 var rp = new OpenIdRelyingParty(store);
1118 // Only set RequireSsl to true, as we don't want to override
1119 // a .config setting of true with false.
1120 if (this.RequireSsl) {
1121 rp.SecuritySettings.RequireSsl = true;
1123 return rp;