Added sreg client javascript support for responses.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / RelyingParty / OpenIdAjaxTextBox.cs
blob517280038ce2f5fa5b381fc97cf80af120a221c6
1 //-----------------------------------------------------------------------
2 // <copyright file="OpenIdAjaxTextBox.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, "text/javascript")]
8 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedDotNetOpenIdLogoResourceName, "image/gif")]
9 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName, "image/gif")]
10 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName, "image/png")]
11 [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginFailureResourceName, "image/png")]
13 #pragma warning disable 0809 // marking inherited, unsupported properties as obsolete to discourage their use
15 namespace DotNetOpenAuth.OpenId.RelyingParty {
16 using System;
17 using System.Collections.Generic;
18 using System.Collections.Specialized;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Diagnostics.CodeAnalysis;
22 using System.Globalization;
23 using System.Linq;
24 using System.Text;
25 using System.Text.RegularExpressions;
26 using System.Web;
27 using System.Web.UI;
28 using System.Web.UI.WebControls;
29 using DotNetOpenAuth.Messaging;
30 using DotNetOpenAuth.OpenId.ChannelElements;
31 using DotNetOpenAuth.OpenId.Extensions;
33 /// <summary>
34 /// An ASP.NET control that provides a minimal text box that is OpenID-aware and uses AJAX for
35 /// a premium login experience.
36 /// </summary>
37 [DefaultProperty("Text"), ValidationProperty("Text")]
38 [ToolboxData("<{0}:OpenIdAjaxTextBox runat=\"server\" />")]
39 public class OpenIdAjaxTextBox : WebControl, ICallbackEventHandler {
40 /// <summary>
41 /// The name of the manifest stream containing the OpenIdAjaxTextBox.js file.
42 /// </summary>
43 internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.js";
45 /// <summary>
46 /// The name of the manifest stream containing the dotnetopenid_16x16.gif file.
47 /// </summary>
48 internal const string EmbeddedDotNetOpenIdLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.dotnetopenid_16x16.gif";
50 /// <summary>
51 /// The name of the manifest stream containing the spinner.gif file.
52 /// </summary>
53 internal const string EmbeddedSpinnerResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.spinner.gif";
55 /// <summary>
56 /// The name of the manifest stream containing the login_success.png file.
57 /// </summary>
58 internal const string EmbeddedLoginSuccessResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_success.png";
60 /// <summary>
61 /// The name of the manifest stream containing the login_failure.png file.
62 /// </summary>
63 internal const string EmbeddedLoginFailureResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_failure.png";
65 #region Property viewstate keys
67 /// <summary>
68 /// The viewstate key to use for storing the value of the <see cref="Columns"/> property.
69 /// </summary>
70 private const string ColumnsViewStateKey = "Columns";
72 /// <summary>
73 /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property.
74 /// </summary>
75 private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived";
77 /// <summary>
78 /// The viewstate key to use for storing the value of the <see cref="AuthenticationResponse"/> property.
79 /// </summary>
80 private const string AuthenticationResponseViewStateKey = "AuthenticationResponse";
82 /// <summary>
83 /// The viewstate key to use for storing the value of the a successful authentication.
84 /// </summary>
85 private const string AuthDataViewStateKey = "AuthData";
87 /// <summary>
88 /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property.
89 /// </summary>
90 private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip";
92 /// <summary>
93 /// The viewstate key to use for storing the value of the <see cref="AuthenticationSucceededToolTip"/> property.
94 /// </summary>
95 private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip";
97 /// <summary>
98 /// The viewstate key to use for storing the value of the <see cref="ReturnToUrl"/> property.
99 /// </summary>
100 private const string ReturnToUrlViewStateKey = "ReturnToUrl";
102 /// <summary>
103 /// The viewstate key to use for storing the value of the <see cref="RealmUrl"/> property.
104 /// </summary>
105 private const string RealmUrlViewStateKey = "RealmUrl";
107 /// <summary>
108 /// The viewstate key to use for storing the value of the <see cref="LogOnInProgressMessage"/> property.
109 /// </summary>
110 private const string LogOnInProgressMessageViewStateKey = "BusyToolTip";
112 /// <summary>
113 /// The viewstate key to use for storing the value of the <see cref="AuthenticationFailedToolTip"/> property.
114 /// </summary>
115 private const string AuthenticationFailedToolTipViewStateKey = "AuthenticationFailedToolTip";
117 /// <summary>
118 /// The viewstate key to use for storing the value of the <see cref="IdentifierRequiredMessage"/> property.
119 /// </summary>
120 private const string IdentifierRequiredMessageViewStateKey = "BusyToolTip";
122 /// <summary>
123 /// The viewstate key to use for storing the value of the <see cref="BusyToolTip"/> property.
124 /// </summary>
125 private const string BusyToolTipViewStateKey = "BusyToolTip";
127 /// <summary>
128 /// The viewstate key to use for storing the value of the <see cref="LogOnText"/> property.
129 /// </summary>
130 private const string LogOnTextViewStateKey = "LoginText";
132 /// <summary>
133 /// The viewstate key to use for storing the value of the <see cref="Throttle"/> property.
134 /// </summary>
135 private const string ThrottleViewStateKey = "Throttle";
137 /// <summary>
138 /// The viewstate key to use for storing the value of the <see cref="LogOnToolTip"/> property.
139 /// </summary>
140 private const string LogOnToolTipViewStateKey = "LoginToolTip";
142 /// <summary>
143 /// The viewstate key to use for storing the value of the <see cref="Name"/> property.
144 /// </summary>
145 private const string NameViewStateKey = "Name";
147 /// <summary>
148 /// The viewstate key to use for storing the value of the <see cref="Timeout"/> property.
149 /// </summary>
150 private const string TimeoutViewStateKey = "Timeout";
152 /// <summary>
153 /// The viewstate key to use for storing the value of the <see cref="Text"/> property.
154 /// </summary>
155 private const string TextViewStateKey = "Text";
157 /// <summary>
158 /// The viewstate key to use for storing the value of the <see cref="TabIndex"/> property.
159 /// </summary>
160 private const string TabIndexViewStateKey = "TabIndex";
162 /// <summary>
163 /// The viewstate key to use for storing the value of the <see cref="RetryToolTip"/> property.
164 /// </summary>
165 private const string RetryToolTipViewStateKey = "RetryToolTip";
167 /// <summary>
168 /// The viewstate key to use for storing the value of the <see cref="RetryText"/> property.
169 /// </summary>
170 private const string RetryTextViewStateKey = "RetryText";
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="ReturnToUrl"/> property.
183 /// </summary>
184 private const string ReturnToUrlDefault = "";
186 /// <summary>
187 /// The default value for the <see cref="RealmUrl"/> property.
188 /// </summary>
189 private const string RealmUrlDefault = "~/";
191 /// <summary>
192 /// The default value for the <see cref="LogOnInProgressMessage"/> property.
193 /// </summary>
194 private const string LogOnInProgressMessageDefault = "Please wait for login to complete.";
196 /// <summary>
197 /// The default value for the <see cref="AuthenticationSucceededToolTip"/> property.
198 /// </summary>
199 private const string AuthenticationSucceededToolTipDefault = "Authenticated by {0}.";
201 /// <summary>
202 /// The default value for the <see cref="AuthenticatedAsToolTip"/> property.
203 /// </summary>
204 private const string AuthenticatedAsToolTipDefault = "Authenticated as {0}.";
206 /// <summary>
207 /// The default value for the <see cref="AuthenticationFailedToolTip"/> property.
208 /// </summary>
209 private const string AuthenticationFailedToolTipDefault = "Authentication failed.";
211 /// <summary>
212 /// The default value for the <see cref="Throttle"/> property.
213 /// </summary>
214 private const int ThrottleDefault = 3;
216 /// <summary>
217 /// The default value for the <see cref="LogOnText"/> property.
218 /// </summary>
219 private const string LogOnTextDefault = "LOG IN";
221 /// <summary>
222 /// The default value for the <see cref="BusyToolTip"/> property.
223 /// </summary>
224 private const string BusyToolTipDefault = "Discovering/authenticating";
226 /// <summary>
227 /// The default value for the <see cref="IdentifierRequiredMessage"/> property.
228 /// </summary>
229 private const string IdentifierRequiredMessageDefault = "Please correct errors in OpenID identifier and allow login to complete before submitting.";
231 /// <summary>
232 /// The default value for the <see cref="Name"/> property.
233 /// </summary>
234 private const string NameDefault = "openid_identifier";
236 /// <summary>
237 /// Default value for <see cref="TabIndex"/> property.
238 /// </summary>
239 private const short TabIndexDefault = 0;
241 /// <summary>
242 /// The default value for the <see cref="RetryToolTip"/> property.
243 /// </summary>
244 private const string RetryToolTipDefault = "Retry a failed identifier discovery.";
246 /// <summary>
247 /// The default value for the <see cref="LogOnToolTip"/> property.
248 /// </summary>
249 private const string LogOnToolTipDefault = "Click here to log in using a pop-up window.";
251 /// <summary>
252 /// The default value for the <see cref="RetryText"/> property.
253 /// </summary>
254 private const string RetryTextDefault = "RETRY";
256 #endregion
258 /// <summary>
259 /// Tracks whether the text box should receive input focus when the page is rendered.
260 /// </summary>
261 private bool focusCalled;
263 /// <summary>
264 /// The authentication response that just came in.
265 /// </summary>
266 private IAuthenticationResponse authenticationResponse;
268 /// <summary>
269 /// A dictionary of extension response types and the javascript member
270 /// name to map them to on the user agent.
271 /// </summary>
272 private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>();
274 /// <summary>
275 /// Stores the result of an AJAX discovery request while it is waiting
276 /// to be picked up by ASP.NET on the way down to the user agent.
277 /// </summary>
278 private string discoveryResult;
280 #region Events
282 /// <summary>
283 /// Fired when the user has typed in their identifier, discovery was successful
284 /// and a login attempt is about to begin.
285 /// </summary>
286 [Description("Fired when the user has typed in their identifier, discovery was successful and a login attempt is about to begin.")]
287 public event EventHandler<OpenIdEventArgs> LoggingIn;
289 /// <summary>
290 /// Fired when a Provider sends back a positive assertion to this control,
291 /// but the authentication has not yet been verified.
292 /// </summary>
293 /// <remarks>
294 /// <b>No security critical decisions should be made within event handlers
295 /// for this event</b> as the authenticity of the assertion has not been
296 /// verified yet. All security related code should go in the event handler
297 /// for the <see cref="LoggedIn"/> event.
298 /// </remarks>
299 [Description("Fired when a Provider sends back a positive assertion to this control, but the authentication has not yet been verified.")]
300 public event EventHandler<OpenIdEventArgs> UnconfirmedPositiveAssertion;
302 /// <summary>
303 /// Fired when authentication has completed successfully.
304 /// </summary>
305 [Description("Fired when authentication has completed successfully.")]
306 public event EventHandler<OpenIdEventArgs> LoggedIn;
308 /// <summary>
309 /// Gets or sets the client-side script that executes when an authentication
310 /// assertion is received (but before it is verified).
311 /// </summary>
312 /// <remarks>
313 /// <para>In the context of the executing javascript set in this property, the
314 /// local variable <i>sender</i> is set to the openid_identifier input box
315 /// that is executing this code.
316 /// This variable has a getClaimedIdentifier() method that may be used to
317 /// identify the user who is being authenticated.</para>
318 /// <para>It is <b>very</b> important to note that when this code executes,
319 /// the authentication has not been verified and may have been spoofed.
320 /// No security-sensitive operations should take place in this javascript code.
321 /// The authentication is verified on the server by the time the
322 /// <see cref="LoggedIn"/> server-side event fires.</para>
323 /// </remarks>
324 [Description("Gets or sets the client-side script that executes when an authentication assertion is received (but before it is verified).")]
325 [Bindable(true), DefaultValue(""), Category("Behavior")]
326 public string OnClientAssertionReceived {
327 get { return this.ViewState[OnClientAssertionReceivedViewStateKey] as string; }
328 set { this.ViewState[OnClientAssertionReceivedViewStateKey] = value; }
331 #endregion
333 #region Properties
335 /// <summary>
336 /// Gets the completed authentication response.
337 /// </summary>
338 public IAuthenticationResponse AuthenticationResponse {
339 get {
340 if (this.authenticationResponse == null) {
341 // We will either validate a new response and return a live AuthenticationResponse
342 // or we will try to deserialize a previous IAuthenticationResponse (snapshot)
343 // from viewstate and return that.
344 IAuthenticationResponse viewstateResponse = this.ViewState[AuthenticationResponseViewStateKey] as IAuthenticationResponse;
345 string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string;
346 string formAuthData = this.Page.Request.Form[this.OpenIdAuthDataFormKey];
348 // First see if there is fresh auth data to be processed into a response.
349 if (!string.IsNullOrEmpty(formAuthData) && !string.Equals(viewstateAuthData, formAuthData, StringComparison.Ordinal)) {
350 this.ViewState[AuthDataViewStateKey] = formAuthData;
352 Uri authUri = new Uri(formAuthData);
353 HttpRequestInfo clientResponseInfo = new HttpRequestInfo {
354 Url = authUri,
356 var rp = CreateRelyingParty(true);
357 this.authenticationResponse = rp.GetResponse(clientResponseInfo);
359 // Save out the authentication response to viewstate so we can find it on
360 // a subsequent postback.
361 this.ViewState[AuthenticationResponseViewStateKey] = this.authenticationResponse;
362 } else {
363 this.authenticationResponse = viewstateResponse;
366 return this.authenticationResponse;
370 /// <summary>
371 /// Gets or sets the value in the text field, completely unprocessed or normalized.
372 /// </summary>
373 [Bindable(true), DefaultValue(""), Category("Appearance")]
374 [Description("The value in the text field, completely unprocessed or normalized.")]
375 public string Text {
376 get { return (string)(this.ViewState[TextViewStateKey] ?? string.Empty); }
377 set { this.ViewState[TextViewStateKey] = value ?? string.Empty; }
380 /// <summary>
381 /// Gets or sets the width of the text box in characters.
382 /// </summary>
383 [Bindable(true), Category("Appearance"), DefaultValue(ColumnsDefault)]
384 [Description("The width of the text box in characters.")]
385 public int Columns {
386 get {
387 return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault);
390 set {
391 ErrorUtilities.VerifyArgumentInRange(value >= 0, "value");
392 this.ViewState[ColumnsViewStateKey] = value;
396 /// <summary>
397 /// Gets or sets the tab index of the text box control. Use 0 to omit an explicit tabindex.
398 /// </summary>
399 [Bindable(true), Category("Behavior"), DefaultValue(TabIndexDefault)]
400 [Description("The tab index of the text box control. Use 0 to omit an explicit tabindex.")]
401 public override short TabIndex {
402 get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); }
403 set { this.ViewState[TabIndexViewStateKey] = value; }
406 /// <summary>
407 /// Gets or sets the HTML name to assign to the text field.
408 /// </summary>
409 [Bindable(true), DefaultValue(NameDefault), Category("Misc")]
410 [Description("The HTML name to assign to the text field.")]
411 public string Name {
412 get {
413 return (string)(this.ViewState[NameViewStateKey] ?? NameDefault);
416 set {
417 ErrorUtilities.VerifyNonZeroLength(value, "value");
418 this.ViewState[NameViewStateKey] = value ?? string.Empty;
422 /// <summary>
423 /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.
424 /// </summary>
425 [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:01"), Category("Behavior")]
426 [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")]
427 public TimeSpan Timeout {
428 get {
429 return (TimeSpan)(this.ViewState[TimeoutViewStateKey] ?? TimeoutDefault);
432 set {
433 ErrorUtilities.VerifyArgumentInRange(value.TotalMilliseconds > 0, "value");
434 this.ViewState[TimeoutViewStateKey] = value;
438 /// <summary>
439 /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with.
440 /// </summary>
441 [Browsable(true), DefaultValue(ThrottleDefault), Category("Behavior")]
442 [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")]
443 public int Throttle {
444 get {
445 return (int)(this.ViewState[ThrottleViewStateKey] ?? ThrottleDefault);
448 set {
449 ErrorUtilities.VerifyArgumentInRange(value > 0, "value");
450 this.ViewState[ThrottleViewStateKey] = value;
454 /// <summary>
455 /// Gets or sets the text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.
456 /// </summary>
457 [Bindable(true), DefaultValue(LogOnTextDefault), Localizable(true), Category("Appearance")]
458 [Description("The text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")]
459 public string LogOnText {
460 get {
461 return (string)(this.ViewState[LogOnTextViewStateKey] ?? LogOnTextDefault);
464 set {
465 ErrorUtilities.VerifyNonZeroLength(value, "value");
466 this.ViewState[LogOnTextViewStateKey] = value ?? string.Empty;
470 /// <summary>
471 /// Gets or sets the rool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.
472 /// </summary>
473 [Bindable(true), DefaultValue(LogOnToolTipDefault), Localizable(true), Category("Appearance")]
474 [Description("The tool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")]
475 public string LogOnToolTip {
476 get { return (string)(this.ViewState[LogOnToolTipViewStateKey] ?? LogOnToolTipDefault); }
477 set { this.ViewState[LogOnToolTipViewStateKey] = value ?? string.Empty; }
480 /// <summary>
481 /// Gets or sets the text that appears on the RETRY button in cases where authentication times out.
482 /// </summary>
483 [Bindable(true), DefaultValue(RetryTextDefault), Localizable(true), Category("Appearance")]
484 [Description("The text that appears on the RETRY button in cases where authentication times out.")]
485 public string RetryText {
486 get {
487 return (string)(this.ViewState[RetryTextViewStateKey] ?? RetryTextDefault);
490 set {
491 ErrorUtilities.VerifyNonZeroLength(value, "value");
492 this.ViewState[RetryTextViewStateKey] = value ?? string.Empty;
496 /// <summary>
497 /// Gets or sets the tool tip text that appears on the RETRY button in cases where authentication times out.
498 /// </summary>
499 [Bindable(true), DefaultValue(RetryToolTipDefault), Localizable(true), Category("Appearance")]
500 [Description("The tool tip text that appears on the RETRY button in cases where authentication times out.")]
501 public string RetryToolTip {
502 get { return (string)(this.ViewState[RetryToolTipViewStateKey] ?? RetryToolTipDefault); }
503 set { this.ViewState[RetryToolTipViewStateKey] = value ?? string.Empty; }
506 /// <summary>
507 /// Gets or sets the tool tip text that appears when authentication succeeds.
508 /// </summary>
509 [Bindable(true), DefaultValue(AuthenticationSucceededToolTipDefault), Localizable(true), Category("Appearance")]
510 [Description("The tool tip text that appears when authentication succeeds.")]
511 public string AuthenticationSucceededToolTip {
512 get { return (string)(this.ViewState[AuthenticationSucceededToolTipViewStateKey] ?? AuthenticationSucceededToolTipDefault); }
513 set { this.ViewState[AuthenticationSucceededToolTipViewStateKey] = value ?? string.Empty; }
516 /// <summary>
517 /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds.
518 /// </summary>
519 [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category("Appearance")]
520 [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")]
521 public string AuthenticatedAsToolTip {
522 get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); }
523 set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; }
526 /// <summary>
527 /// Gets or sets the tool tip text that appears when authentication fails.
528 /// </summary>
529 [Bindable(true), DefaultValue(AuthenticationFailedToolTipDefault), Localizable(true), Category("Appearance")]
530 [Description("The tool tip text that appears when authentication fails.")]
531 public string AuthenticationFailedToolTip {
532 get { return (string)(this.ViewState[AuthenticationFailedToolTipViewStateKey] ?? AuthenticationFailedToolTipDefault); }
533 set { this.ViewState[AuthenticationFailedToolTipViewStateKey] = value ?? string.Empty; }
536 /// <summary>
537 /// Gets or sets the tool tip text that appears over the text box when it is discovering and authenticating.
538 /// </summary>
539 [Bindable(true), DefaultValue(BusyToolTipDefault), Localizable(true), Category("Appearance")]
540 [Description("The tool tip text that appears over the text box when it is discovering and authenticating.")]
541 public string BusyToolTip {
542 get { return (string)(this.ViewState[BusyToolTipViewStateKey] ?? BusyToolTipDefault); }
543 set { this.ViewState[BusyToolTipViewStateKey] = value ?? string.Empty; }
546 /// <summary>
547 /// Gets or sets the message that is displayed if a postback is about to occur before the identifier has been supplied.
548 /// </summary>
549 [Bindable(true), DefaultValue(IdentifierRequiredMessageDefault), Localizable(true), Category("Appearance")]
550 [Description("The message that is displayed if a postback is about to occur before the identifier has been supplied.")]
551 public string IdentifierRequiredMessage {
552 get { return (string)(this.ViewState[IdentifierRequiredMessageViewStateKey] ?? IdentifierRequiredMessageDefault); }
553 set { this.ViewState[IdentifierRequiredMessageViewStateKey] = value ?? string.Empty; }
556 /// <summary>
557 /// Gets or sets the message that is displayed if a postback is attempted while login is in process.
558 /// </summary>
559 [Bindable(true), DefaultValue(LogOnInProgressMessageDefault), Localizable(true), Category("Appearance")]
560 [Description("The message that is displayed if a postback is attempted while login is in process.")]
561 public string LogOnInProgressMessage {
562 get { return (string)(this.ViewState[LogOnInProgressMessageViewStateKey] ?? LogOnInProgressMessageDefault); }
563 set { this.ViewState[LogOnInProgressMessageViewStateKey] = value ?? string.Empty; }
566 /// <summary>
567 /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site.
568 /// </summary>
569 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
570 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenId.Realm", Justification = "Using ctor for validation.")]
571 [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Property grid on form designer only supports primitive types.")]
572 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid on form designer only supports primitive types.")]
573 [Bindable(true)]
574 [Category("Behavior")]
575 [DefaultValue(RealmUrlDefault)]
576 [Description("The OpenID Realm of the relying party web site.")]
577 public string RealmUrl {
578 get {
579 return (string)(this.ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault);
582 set {
583 if (Page != null && !DesignMode) {
584 // Validate new value by trying to construct a Realm object based on it.
585 new Realm(OpenIdUtilities.GetResolvedRealm(Page, value)); // throws an exception on failure.
586 } else {
587 // We can't fully test it, but it should start with either ~/ or a protocol.
588 if (Regex.IsMatch(value, @"^https?://")) {
589 new Uri(value.Replace("*.", "")); // make sure it's fully-qualified, but ignore wildcards
590 } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
591 // this is valid too
592 } else {
593 throw new UriFormatException();
596 this.ViewState[RealmUrlViewStateKey] = value;
600 /// <summary>
601 /// Gets or sets the OpenID ReturnTo of the relying party web site.
602 /// </summary>
603 [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")]
604 [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Property grid on form designer only supports primitive types.")]
605 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid on form designer only supports primitive types.")]
606 [Bindable(true)]
607 [Category("Behavior")]
608 [DefaultValue(ReturnToUrlDefault)]
609 [Description("The OpenID ReturnTo of the relying party web site.")]
610 public string ReturnToUrl {
611 get {
612 return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault);
615 set {
616 if (Page != null && !DesignMode) {
617 // Validate new value by trying to construct a Uri based on it.
618 new Uri(MessagingUtilities.GetRequestUrlFromContext(), Page.ResolveUrl(value)); // throws an exception on failure.
619 } else {
620 // We can't fully test it, but it should start with either ~/ or a protocol.
621 if (Regex.IsMatch(value, @"^https?://")) {
622 new Uri(value); // make sure it's fully-qualified, but ignore wildcards
623 } else if (value.StartsWith("~/", StringComparison.Ordinal)) {
624 // this is valid too
625 } else {
626 throw new UriFormatException();
629 this.ViewState[ReturnToUrlViewStateKey] = value;
633 #endregion
635 #region Properties to hide
637 /// <summary>
638 /// Gets or sets the foreground color (typically the color of the text) of the Web server control.
639 /// </summary>
640 /// <returns>
641 /// 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"/>.
642 /// </returns>
643 [Obsolete, Browsable(false), Bindable(false)]
644 public override System.Drawing.Color ForeColor {
645 get { throw new NotSupportedException(); }
646 set { throw new NotSupportedException(); }
649 /// <summary>
650 /// Gets or sets the background color of the Web server control.
651 /// </summary>
652 /// <returns>
653 /// 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.
654 /// </returns>
655 [Obsolete, Browsable(false), Bindable(false)]
656 public override System.Drawing.Color BackColor {
657 get { throw new NotSupportedException(); }
658 set { throw new NotSupportedException(); }
661 /// <summary>
662 /// Gets or sets the border color of the Web control.
663 /// </summary>
664 /// <returns>
665 /// 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.
666 /// </returns>
667 [Obsolete, Browsable(false), Bindable(false)]
668 public override System.Drawing.Color BorderColor {
669 get { throw new NotSupportedException(); }
670 set { throw new NotSupportedException(); }
673 /// <summary>
674 /// Gets or sets the border width of the Web server control.
675 /// </summary>
676 /// <returns>
677 /// 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.
678 /// </returns>
679 /// <exception cref="T:System.ArgumentException">
680 /// The specified border width is a negative value.
681 /// </exception>
682 [Obsolete, Browsable(false), Bindable(false)]
683 public override Unit BorderWidth {
684 get { return Unit.Empty; }
685 set { throw new NotSupportedException(); }
688 /// <summary>
689 /// Gets or sets the border style of the Web server control.
690 /// </summary>
691 /// <returns>
692 /// One of the <see cref="T:System.Web.UI.WebControls.BorderStyle"/> enumeration values. The default is NotSet.
693 /// </returns>
694 [Obsolete, Browsable(false), Bindable(false)]
695 public override BorderStyle BorderStyle {
696 get { return BorderStyle.None; }
697 set { throw new NotSupportedException(); }
700 /// <summary>
701 /// Gets the font properties associated with the Web server control.
702 /// </summary>
703 /// <returns>
704 /// A <see cref="T:System.Web.UI.WebControls.FontInfo"/> that represents the font properties of the Web server control.
705 /// </returns>
706 [Obsolete, Browsable(false), Bindable(false)]
707 public override FontInfo Font {
708 get { return null; }
711 /// <summary>
712 /// Gets or sets the height of the Web server control.
713 /// </summary>
714 /// <returns>
715 /// 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"/>.
716 /// </returns>
717 /// <exception cref="T:System.ArgumentException">
718 /// The height was set to a negative value.
719 /// </exception>
720 [Obsolete, Browsable(false), Bindable(false)]
721 public override Unit Height {
722 get { return Unit.Empty; }
723 set { throw new NotSupportedException(); }
726 /// <summary>
727 /// Gets or sets the width of the Web server control.
728 /// </summary>
729 /// <returns>
730 /// 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"/>.
731 /// </returns>
732 /// <exception cref="T:System.ArgumentException">
733 /// The width of the Web server control was set to a negative value.
734 /// </exception>
735 [Obsolete, Browsable(false), Bindable(false)]
736 public override Unit Width {
737 get { return Unit.Empty; }
738 set { throw new NotSupportedException(); }
741 /// <summary>
742 /// Gets or sets the text displayed when the mouse pointer hovers over the Web server control.
743 /// </summary>
744 /// <returns>
745 /// The text displayed when the mouse pointer hovers over the Web server control. The default is <see cref="F:System.String.Empty"/>.
746 /// </returns>
747 [Obsolete, Browsable(false), Bindable(false)]
748 public override string ToolTip {
749 get { return string.Empty; }
750 set { throw new NotSupportedException(); }
753 /// <summary>
754 /// Gets or sets the skin to apply to the control.
755 /// </summary>
756 /// <returns>
757 /// The name of the skin to apply to the control. The default is <see cref="F:System.String.Empty"/>.
758 /// </returns>
759 /// <exception cref="T:System.ArgumentException">
760 /// The skin specified in the <see cref="P:System.Web.UI.WebControls.WebControl.SkinID"/> property does not exist in the theme.
761 /// </exception>
762 [Obsolete, Browsable(false), Bindable(false)]
763 public override string SkinID {
764 get { return string.Empty; }
765 set { throw new NotSupportedException(); }
768 /// <summary>
769 /// Gets or sets a value indicating whether themes apply to this control.
770 /// </summary>
771 /// <returns>true to use themes; otherwise, false. The default is false.
772 /// </returns>
773 [Obsolete, Browsable(false), Bindable(false)]
774 public override bool EnableTheming {
775 get { return false; }
776 set { throw new NotSupportedException(); }
779 #endregion
781 /// <summary>
782 /// Gets the default value for the <see cref="Timeout"/> property.
783 /// </summary>
784 /// <value>8 seconds; or eternity if the debugger is attached.</value>
785 private static TimeSpan TimeoutDefault {
786 get {
787 if (Debugger.IsAttached) {
788 Logger.Warn("Debugger is attached. Inflating default OpenIdAjaxTextbox.Timeout value to infinity.");
789 return TimeSpan.MaxValue;
790 } else {
791 return TimeSpan.FromSeconds(8);
796 /// <summary>
797 /// Gets the name of the open id auth data form key.
798 /// </summary>
799 /// <value>A concatenation of <see cref="Name"/> and <c>"_openidAuthData"</c>.</value>
800 private string OpenIdAuthDataFormKey {
801 get { return this.Name + "_openidAuthData"; }
804 /// <summary>
805 /// Places focus on the text box when the page is rendered on the browser.
806 /// </summary>
807 public override void Focus() {
808 // we don't emit the code to focus the control immediately, in case the control
809 // is never rendered to the page because its Visible property is false or that
810 // of any of its parent containers.
811 this.focusCalled = true;
814 /// <summary>
815 /// Allows an OpenID extension to read data out of an unverified positive authentication assertion
816 /// and send it down to the client browser so that Javascript running on the page can perform
817 /// some preprocessing on the extension data.
818 /// </summary>
819 /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam>
820 /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param>
821 /// <remarks>
822 /// This method should be called from the <see cref="UnconfirmedPositiveAssertion"/> event handler.
823 /// </remarks>
824 [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")]
825 public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse {
826 ErrorUtilities.VerifyNonZeroLength(propertyName, "propertyName");
827 ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName);
828 foreach (var ext in this.clientScriptExtensions.Keys) {
829 ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName);
831 this.clientScriptExtensions.Add(typeof(T), propertyName);
834 #region ICallbackEventHandler Members
836 /// <summary>
837 /// Returns the result of discovery on some Identifier passed to <see cref="ICallbackEventHandler.RaiseCallbackEvent"/>.
838 /// </summary>
839 /// <returns>The result of the callback.</returns>
840 /// <value>A whitespace delimited list of URLs that can be used to initiate authentication.</value>
841 string ICallbackEventHandler.GetCallbackResult() {
842 this.Page.Response.ContentType = "text/javascript";
843 return this.discoveryResult;
846 /// <summary>
847 /// Performs discovery on some OpenID Identifier. Called directly from the user agent via
848 /// AJAX callback mechanisms.
849 /// </summary>
850 /// <param name="eventArgument">The identifier to perform discovery on.</param>
851 void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) {
852 string userSuppliedIdentifier = eventArgument;
854 ErrorUtilities.VerifyNonZeroLength(userSuppliedIdentifier, "userSuppliedIdentifier");
855 Logger.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier);
857 // We prepare a JSON object with this interface:
858 // class jsonResponse {
859 // string claimedIdentifier;
860 // Array requests; // never null
861 // string error; // null if no error
862 // }
863 // Each element in the requests array looks like this:
864 // class jsonAuthRequest {
865 // string endpoint; // URL to the OP endpoint
866 // string immediate; // URL to initiate an immediate request
867 // string setup; // URL to initiate a setup request.
868 // }
869 StringBuilder discoveryResultBuilder = new StringBuilder();
870 discoveryResultBuilder.Append("{");
871 try {
872 List<IAuthenticationRequest> requests = this.CreateRequests(userSuppliedIdentifier, true);
873 if (requests.Count > 0) {
874 discoveryResultBuilder.AppendFormat("claimedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(requests[0].ClaimedIdentifier));
875 discoveryResultBuilder.Append("requests: [");
876 foreach (IAuthenticationRequest request in requests) {
877 this.OnLoggingIn(request);
878 discoveryResultBuilder.Append("{");
879 discoveryResultBuilder.AppendFormat("endpoint: {0},", MessagingUtilities.GetSafeJavascriptValue(request.Provider.Uri.AbsoluteUri));
880 request.Mode = AuthenticationRequestMode.Immediate;
881 UserAgentResponse response = request.RedirectingResponse;
882 discoveryResultBuilder.AppendFormat("immediate: {0},", MessagingUtilities.GetSafeJavascriptValue(response.DirectUriRequest.AbsoluteUri));
883 request.Mode = AuthenticationRequestMode.Setup;
884 response = request.RedirectingResponse;
885 discoveryResultBuilder.AppendFormat("setup: {0}", MessagingUtilities.GetSafeJavascriptValue(response.DirectUriRequest.AbsoluteUri));
886 discoveryResultBuilder.Append("},");
888 discoveryResultBuilder.Length -= 1; // trim off last comma
889 discoveryResultBuilder.Append("]");
890 } else {
891 discoveryResultBuilder.Append("requests: new Array(),");
892 discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(OpenIdStrings.OpenIdEndpointNotFound));
894 } catch (ProtocolException ex) {
895 discoveryResultBuilder.Append("requests: new Array(),");
896 discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(ex.Message));
898 discoveryResultBuilder.Append("}");
899 this.discoveryResult = discoveryResultBuilder.ToString();
902 #endregion
904 /// <summary>
905 /// Fires the <see cref="LoggingIn"/> event.
906 /// </summary>
907 /// <param name="request">The request.</param>
908 protected virtual void OnLoggingIn(IAuthenticationRequest request) {
909 var loggingIn = this.LoggingIn;
910 if (loggingIn != null) {
911 loggingIn(this, new OpenIdEventArgs(request));
915 /// <summary>
916 /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event.
917 /// </summary>
918 protected virtual void OnUnconfirmedPositiveAssertion() {
919 var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion;
920 if (unconfirmedPositiveAssertion != null) {
921 unconfirmedPositiveAssertion(this, null);
925 /// <summary>
926 /// Fires the <see cref="LoggedIn"/> event.
927 /// </summary>
928 /// <param name="response">The response.</param>
929 protected virtual void OnLoggedIn(IAuthenticationResponse response) {
930 var loggedIn = this.LoggedIn;
931 if (loggedIn != null) {
932 loggedIn(this, new OpenIdEventArgs(response));
936 /// <summary>
937 /// Prepares the control for loading.
938 /// </summary>
939 /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
940 protected override void OnLoad(EventArgs e) {
941 base.OnLoad(e);
943 if (this.Page.IsPostBack) {
944 // If the control was temporarily hidden, it won't be in the Form data,
945 // and we'll just implicitly keep the last Text setting.
946 if (this.Page.Request.Form[this.Name] != null) {
947 this.Text = this.Page.Request.Form[this.Name];
950 // If there is a response, and it is fresh (live object, not a snapshot object)...
951 if (this.AuthenticationResponse != null && this.AuthenticationResponse.Status == AuthenticationStatus.Authenticated) {
952 this.OnLoggedIn(this.AuthenticationResponse);
954 } else {
955 NameValueCollection query = MessagingUtilities.GetQueryOrFormFromContext();
956 string userSuppliedIdentifier = query["dotnetopenid.userSuppliedIdentifier"];
957 if (!string.IsNullOrEmpty(userSuppliedIdentifier) && query["dotnetopenid.phase"] == "2") {
958 this.ReportAuthenticationResult();
963 /// <summary>
964 /// Prepares to render the control.
965 /// </summary>
966 /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
967 protected override void OnPreRender(EventArgs e) {
968 base.OnPreRender(e);
970 this.PrepareClientJavascript();
973 /// <summary>
974 /// Renders the control.
975 /// </summary>
976 /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.</param>
977 protected override void Render(System.Web.UI.HtmlTextWriter writer) {
978 // We surround the textbox with a span so that the .js file can inject a
979 // login button within the text box with easy placement.
980 writer.WriteBeginTag("span");
981 writer.WriteAttribute("class", this.CssClass);
982 writer.Write(" style='");
983 writer.WriteStyleAttribute("position", "relative");
984 writer.WriteStyleAttribute("font-size", "16px");
985 writer.Write("'>");
987 writer.WriteBeginTag("input");
988 writer.WriteAttribute("name", this.Name);
989 writer.WriteAttribute("id", this.ClientID);
990 writer.WriteAttribute("value", this.Text, true);
991 writer.WriteAttribute("size", this.Columns.ToString(CultureInfo.InvariantCulture));
992 if (this.TabIndex > 0) {
993 writer.WriteAttribute("tabindex", this.TabIndex.ToString(CultureInfo.InvariantCulture));
995 if (!this.Enabled) {
996 writer.WriteAttribute("disabled", "true");
998 if (!string.IsNullOrEmpty(this.CssClass)) {
999 writer.WriteAttribute("class", this.CssClass);
1001 writer.Write(" style='");
1002 writer.WriteStyleAttribute("padding-left", "18px");
1003 writer.WriteStyleAttribute("border-style", "solid");
1004 writer.WriteStyleAttribute("border-width", "1px");
1005 writer.WriteStyleAttribute("border-color", "lightgray");
1006 writer.Write("'");
1007 writer.Write(" />");
1009 writer.WriteEndTag("span");
1011 // Emit a hidden field to let the javascript on the user agent know if an
1012 // authentication has already successfully taken place.
1013 string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string;
1014 if (!string.IsNullOrEmpty(viewstateAuthData)) {
1015 writer.WriteBeginTag("input");
1016 writer.WriteAttribute("type", "hidden");
1017 writer.WriteAttribute("name", this.OpenIdAuthDataFormKey);
1018 writer.WriteAttribute("value", viewstateAuthData, true);
1019 writer.Write(" />");
1023 /// <summary>
1024 /// Filters a sequence of OP endpoints so that an OP hostname only appears once in the list.
1025 /// </summary>
1026 /// <param name="requests">The authentication requests against those OP endpoints.</param>
1027 /// <returns>The filtered list.</returns>
1028 private static List<IAuthenticationRequest> RemoveDuplicateEndpoints(List<IAuthenticationRequest> requests) {
1029 var filteredRequests = new List<IAuthenticationRequest>(requests.Count);
1030 foreach (IAuthenticationRequest request in requests) {
1031 // We'll distinguish based on the host name only, which
1032 // admittedly is only a heuristic, but if we remove one that really wasn't a duplicate, well,
1033 // this multiple OP attempt thing was just a convenience feature anyway.
1034 if (!filteredRequests.Any(req => string.Equals(req.Provider.Uri.Host, request.Provider.Uri.Host, StringComparison.OrdinalIgnoreCase))) {
1035 filteredRequests.Add(request);
1039 return filteredRequests;
1042 /// <summary>
1043 /// Creates the relying party.
1044 /// </summary>
1045 /// <param name="verifySignature">
1046 /// A value indicating whether message protections should be applied to the processed messages.
1047 /// Use <c>false</c> to postpone verification to a later time without invalidating nonces.
1048 /// </param>
1049 /// <returns>The newly instantiated relying party.</returns>
1050 private static OpenIdRelyingParty CreateRelyingParty(bool verifySignature) {
1051 return verifySignature ? new OpenIdRelyingParty() : OpenIdRelyingParty.CreateNonVerifying();
1054 /// <summary>
1055 /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox,
1056 /// and closes the calling popup window if applicable.
1057 /// </summary>
1058 /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including
1059 /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param>
1060 private void CallbackUserAgentMethod(string methodCall) {
1061 this.CallbackUserAgentMethod(methodCall, null);
1064 /// <summary>
1065 /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox,
1066 /// and closes the calling popup window if applicable.
1067 /// </summary>
1068 /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including
1069 /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param>
1070 /// <param name="preAssignments">An optional list of assignments to make to the input box object before placing the method call.</param>
1071 private void CallbackUserAgentMethod(string methodCall, string[] preAssignments) {
1072 Logger.InfoFormat("Sending Javascript callback: {0}", methodCall);
1073 Page.Response.Write(@"<html><body><script language='javascript'>
1074 var inPopup = !window.frameElement;
1075 var objSrc = inPopup ? window.opener.waiting_openidBox : window.frameElement.openidBox;
1077 if (preAssignments != null) {
1078 foreach (string assignment in preAssignments) {
1079 Page.Response.Write(string.Format(CultureInfo.InvariantCulture, " objSrc.{0};\n", assignment));
1083 // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable,
1084 // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already
1085 // whether to call window.self.close() after the call.
1086 string htmlFormat = @" if (inPopup) {{
1087 objSrc.{0};
1088 window.self.close();
1089 }} else {{
1090 objSrc.{0};
1092 </script></body></html>";
1093 Page.Response.Write(string.Format(CultureInfo.InvariantCulture, htmlFormat, methodCall));
1094 Page.Response.End();
1097 /// <summary>
1098 /// Assembles the javascript to send to the client and registers it with ASP.NET for transmission.
1099 /// </summary>
1100 private void PrepareClientJavascript() {
1101 string identifierParameterName = "identifier";
1102 string discoveryCallbackResultParameterName = "resultFunction";
1103 string discoveryErrorCallbackParameterName = "errorCallback";
1104 string discoveryCallback = Page.ClientScript.GetCallbackEventReference(
1105 this,
1106 identifierParameterName,
1107 discoveryCallbackResultParameterName,
1108 identifierParameterName,
1109 discoveryErrorCallbackParameterName,
1110 true);
1112 // Import the .js file where most of the code is.
1113 this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdAjaxTextBox), EmbeddedScriptResourceName);
1115 // Call into the .js file with initialization information.
1116 StringBuilder startupScript = new StringBuilder();
1117 startupScript.AppendLine("<script language='javascript'>");
1118 startupScript.AppendFormat("var box = document.getElementsByName('{0}')[0];{1}", this.Name, Environment.NewLine);
1119 if (this.focusCalled) {
1120 startupScript.AppendLine("box.focus();");
1122 startupScript.AppendFormat(
1123 CultureInfo.InvariantCulture,
1124 "initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, function({18}, {19}, {20}) {{{21}}});{22}",
1125 MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), OpenIdTextBox.EmbeddedLogoResourceName)),
1126 MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedDotNetOpenIdLogoResourceName)),
1127 MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedSpinnerResourceName)),
1128 MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginSuccessResourceName)),
1129 MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedLoginFailureResourceName)),
1130 this.Throttle,
1131 this.Timeout.TotalMilliseconds,
1132 string.IsNullOrEmpty(this.OnClientAssertionReceived) ? "null" : "'" + this.OnClientAssertionReceived.Replace(@"\", @"\\").Replace("'", @"\'") + "'",
1133 MessagingUtilities.GetSafeJavascriptValue(this.LogOnText),
1134 MessagingUtilities.GetSafeJavascriptValue(this.LogOnToolTip),
1135 MessagingUtilities.GetSafeJavascriptValue(this.RetryText),
1136 MessagingUtilities.GetSafeJavascriptValue(this.RetryToolTip),
1137 MessagingUtilities.GetSafeJavascriptValue(this.BusyToolTip),
1138 MessagingUtilities.GetSafeJavascriptValue(this.IdentifierRequiredMessage),
1139 MessagingUtilities.GetSafeJavascriptValue(this.LogOnInProgressMessage),
1140 MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationSucceededToolTip),
1141 MessagingUtilities.GetSafeJavascriptValue(this.AuthenticatedAsToolTip),
1142 MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationFailedToolTip),
1143 identifierParameterName,
1144 discoveryCallbackResultParameterName,
1145 discoveryErrorCallbackParameterName,
1146 discoveryCallback,
1147 Environment.NewLine);
1149 startupScript.AppendLine("</script>");
1151 Page.ClientScript.RegisterStartupScript(this.GetType(), "ajaxstartup", startupScript.ToString());
1152 string htmlFormat = @"
1153 var openidbox = document.getElementsByName('{0}')[0];
1154 if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }}
1156 Page.ClientScript.RegisterOnSubmitStatement(
1157 this.GetType(),
1158 "loginvalidation",
1159 string.Format(CultureInfo.InvariantCulture, htmlFormat, this.Name));
1162 /// <summary>
1163 /// Creates the authentication requests for a given user-supplied Identifier.
1164 /// </summary>
1165 /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
1166 /// <param name="immediate">A value indicating whether the authentication
1167 /// requests should be initialized for use in invisible iframes for background authentication.</param>
1168 /// <returns>The list of authentication requests, any one of which may be
1169 /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns>
1170 private List<IAuthenticationRequest> CreateRequests(string userSuppliedIdentifier, bool immediate) {
1171 var requests = new List<IAuthenticationRequest>();
1173 OpenIdRelyingParty rp = CreateRelyingParty(true);
1175 // Resolve the trust root, and swap out the scheme and port if necessary to match the
1176 // return_to URL, since this match is required by OpenId, and the consumer app
1177 // may be using HTTP at some times and HTTPS at others.
1178 UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl);
1179 realm.Scheme = Page.Request.Url.Scheme;
1180 realm.Port = Page.Request.Url.Port;
1182 // Initiate openid request
1183 // We use TryParse here to avoid throwing an exception which
1184 // might slip through our validator control if it is disabled.
1185 Realm typedRealm = new Realm(realm);
1186 if (string.IsNullOrEmpty(this.ReturnToUrl)) {
1187 requests.AddRange(rp.CreateRequests(userSuppliedIdentifier, typedRealm));
1188 } else {
1189 Uri returnTo = new Uri(MessagingUtilities.GetRequestUrlFromContext(), this.ReturnToUrl);
1190 requests.AddRange(rp.CreateRequests(userSuppliedIdentifier, typedRealm, returnTo));
1193 // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example).
1194 // Since we're gathering OPs to try one after the other, just take the first choice of each OP
1195 // and don't try it multiple times.
1196 requests = RemoveDuplicateEndpoints(requests);
1198 // Configure each generated request.
1199 int reqIndex = 0;
1200 foreach (var req in requests) {
1201 req.AddCallbackArguments("index", (reqIndex++).ToString(CultureInfo.InvariantCulture));
1203 // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter
1204 if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)["dotnetopenid.userSuppliedIdentifier"])) {
1205 req.AddCallbackArguments("dotnetopenid.userSuppliedIdentifier", userSuppliedIdentifier);
1208 // Our javascript needs to let the user know which endpoint responded. So we force it here.
1209 // This gives us the info even for 1.0 OPs and 2.0 setup_required responses.
1210 req.AddCallbackArguments("dotnetopenid.op_endpoint", req.Provider.Uri.AbsoluteUri);
1211 req.AddCallbackArguments("dotnetopenid.claimed_id", req.ClaimedIdentifier);
1212 req.AddCallbackArguments("dotnetopenid.phase", "2");
1213 if (immediate) {
1214 req.Mode = AuthenticationRequestMode.Immediate;
1215 ((AuthenticationRequest)req).AssociationPreference = AssociationPreference.IfAlreadyEstablished;
1219 return requests;
1222 /// <summary>
1223 /// Notifies the user agent via an AJAX response of a completed authentication attempt.
1224 /// </summary>
1225 private void ReportAuthenticationResult() {
1226 Logger.InfoFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url);
1227 List<string> assignments = new List<string>();
1229 OpenIdRelyingParty rp = CreateRelyingParty(false);
1230 var f = HttpUtility.ParseQueryString(this.Page.Request.Url.Query).ToDictionary();
1231 var authResponse = rp.GetResponse();
1232 if (authResponse.Status == AuthenticationStatus.Authenticated) {
1233 this.OnUnconfirmedPositiveAssertion();
1234 foreach (var pair in this.clientScriptExtensions) {
1235 IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key);
1236 var positiveResponse = (PositiveAuthenticationResponse)authResponse;
1237 string js = extension.InitializeJavaScriptData(positiveResponse.Response);
1238 if (string.IsNullOrEmpty(js)) {
1239 js = "null";
1241 assignments.Add(pair.Value + " = " + js);
1245 this.CallbackUserAgentMethod("dnoi_internal.processAuthorizationResult(document.URL)", assignments.ToArray());