/********************************************************
* Copyright (C) 2007 Andrew Arnott
* Released under the New BSD License
* License available here: http://www.opensource.org/licenses/bsd-license.php
* For news or support on this file: http://blog.nerdbank.net/
********************************************************/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using System.Web;
using DotNetOpenId.Extensions.SimpleRegistration;
[assembly: WebResource(DotNetOpenId.RelyingParty.OpenIdTextBox.EmbeddedLogoResourceName, "image/gif")]
namespace DotNetOpenId.RelyingParty
{
///
/// An ASP.NET control that provides a minimal text box that is OpenID-aware.
///
///
/// This control offers greater UI flexibility than the
/// control, but requires more work to be done by the hosting web site to
/// assemble a complete login experience.
///
[DefaultProperty("Text")]
[ToolboxData("<{0}:OpenIdTextBox runat=\"server\">{0}:OpenIdTextBox>")]
public class OpenIdTextBox : CompositeControl
{
///
/// Instantiates an instance.
///
public OpenIdTextBox()
{
InitializeControls();
}
internal const string EmbeddedLogoResourceName = DotNetOpenId.Util.DefaultNamespace + ".RelyingParty.openid_login.gif";
TextBox wrappedTextBox;
///
/// Gets the control that this control wraps.
///
protected TextBox WrappedTextBox
{
get { return wrappedTextBox; }
}
///
/// Creates the text box control.
///
protected override void CreateChildControls()
{
base.CreateChildControls();
Controls.Add(wrappedTextBox);
if (ShouldBeFocused)
WrappedTextBox.Focus();
}
///
/// Initializes the text box control.
///
protected virtual void InitializeControls()
{
wrappedTextBox = new TextBox();
wrappedTextBox.ID = "wrappedTextBox";
wrappedTextBox.CssClass = cssClassDefault;
wrappedTextBox.Columns = columnsDefault;
wrappedTextBox.Text = text;
wrappedTextBox.TabIndex = TabIndexDefault;
}
///
/// Whether the text box should receive input focus when the web page appears.
///
protected bool ShouldBeFocused;
///
/// Sets the input focus to start on the text box when the page appears
/// in the user's browser.
///
public override void Focus()
{
if (Controls.Count == 0)
ShouldBeFocused = true;
else
WrappedTextBox.Focus();
}
const string appearanceCategory = "Appearance";
const string profileCategory = "Simple Registration";
const string behaviorCategory = "Behavior";
#region Properties
const string textDefault = "";
string text = textDefault;
///
/// The content of the text box.
///
[Bindable(true)]
[Category(appearanceCategory)]
[DefaultValue("")]
[Description("The content of the text box.")]
public string Text
{
get { return (WrappedTextBox != null) ? WrappedTextBox.Text : text; }
set
{
text = value;
if (WrappedTextBox != null) WrappedTextBox.Text = value;
}
}
const string realmUrlViewStateKey = "RealmUrl";
const string realmUrlDefault = "~/";
///
/// The OpenID of the relying party web site.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenId.Realm"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings"), SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
[Bindable(true)]
[Category(behaviorCategory)]
[DefaultValue(realmUrlDefault)]
[Description("The OpenID Realm of the relying party web site.")]
public string RealmUrl
{
get { return (string)(ViewState[realmUrlViewStateKey] ?? realmUrlDefault); }
set
{
if (Page != null && !DesignMode)
{
// Validate new value by trying to construct a Realm object based on it.
new Realm(getResolvedRealm(value)); // throws an exception on failure.
}
else
{
// We can't fully test it, but it should start with either ~/ or a protocol.
if (Regex.IsMatch(value, @"^https?://"))
{
new Uri(value.Replace("*.", "")); // make sure it's fully-qualified, but ignore wildcards
}
else if (value.StartsWith("~/", StringComparison.Ordinal))
{
// this is valid too
}
else
throw new UriFormatException();
}
ViewState[realmUrlViewStateKey] = value;
}
}
const string immediateModeViewStateKey = "ImmediateMode";
const bool immediateModeDefault = false;
///
/// True if a Provider should reply immediately to the authentication request
/// without interacting with the user. False if the Provider can take time
/// to authenticate the user in order to complete an authentication attempt.
///
///
/// Setting this to true is sometimes useful in AJAX scenarios. Setting this to
/// true can cause failed authentications when the user truly controls an
/// Identifier, but must complete an authentication step with the Provider before
/// the Provider will approve the login from this relying party.
///
[Bindable(true)]
[Category(behaviorCategory)]
[DefaultValue(immediateModeDefault)]
[Description("Whether the Provider should respond immediately to an authentication attempt without interacting with the user.")]
public bool ImmediateMode {
get { return (bool)(ViewState[immediateModeViewStateKey] ?? immediateModeDefault); }
set { ViewState[immediateModeViewStateKey] = value; }
}
const string cssClassDefault = "openid";
///
/// Gets/sets the CSS class assigned to the text box.
///
[Bindable(true)]
[Category(appearanceCategory)]
[DefaultValue(cssClassDefault)]
[Description("The CSS class assigned to the text box.")]
public override string CssClass
{
get { return WrappedTextBox.CssClass; }
set { WrappedTextBox.CssClass = value; }
}
const string showLogoViewStateKey = "ShowLogo";
const bool showLogoDefault = true;
///
/// Gets/sets whether to show the OpenID logo in the text box.
///
[Bindable(true)]
[Category(appearanceCategory)]
[DefaultValue(showLogoDefault)]
[Description("The visibility of the OpenID logo in the text box.")]
public bool ShowLogo {
get { return (bool)(ViewState[showLogoViewStateKey] ?? showLogoDefault); }
set { ViewState[showLogoViewStateKey] = value; }
}
const string usePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie";
const string usePersistentCookieViewStateKey = "UsePersistentCookie";
///
/// Default value of .
///
protected const bool UsePersistentCookieDefault = false;
///
/// Whether to send a persistent cookie upon successful
/// login so the user does not have to log in upon returning to this site.
///
[Bindable(true)]
[Category(behaviorCategory)]
[DefaultValue(UsePersistentCookieDefault)]
[Description("Whether to send a persistent cookie upon successful " +
"login so the user does not have to log in upon returning to this site.")]
public virtual bool UsePersistentCookie
{
get { return (bool)(ViewState[usePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); }
set { ViewState[usePersistentCookieViewStateKey] = value; }
}
const int columnsDefault = 40;
///
/// The width of the text box in characters.
///
[Bindable(true)]
[Category(appearanceCategory)]
[DefaultValue(columnsDefault)]
[Description("The width of the text box in characters.")]
public int Columns
{
get { return WrappedTextBox.Columns; }
set { WrappedTextBox.Columns = value; }
}
///
/// Default value for property.
///
protected const short TabIndexDefault = 0;
///
/// The tab index of the text box control.
///
[Bindable(true)]
[Category(behaviorCategory)]
[DefaultValue(TabIndexDefault)]
[Description("The tab index of the text box control.")]
public override short TabIndex {
get { return WrappedTextBox.TabIndex; }
set { WrappedTextBox.TabIndex = value; }
}
const string requestNicknameViewStateKey = "RequestNickname";
const DemandLevel requestNicknameDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's nickname from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestNicknameDefault)]
[Description("Your level of interest in receiving the user's nickname from the Provider.")]
public DemandLevel RequestNickname
{
get { return (DemandLevel)(ViewState[requestNicknameViewStateKey] ?? requestNicknameDefault); }
set { ViewState[requestNicknameViewStateKey] = value; }
}
const string requestEmailViewStateKey = "RequestEmail";
const DemandLevel requestEmailDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's email address from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestEmailDefault)]
[Description("Your level of interest in receiving the user's email address from the Provider.")]
public DemandLevel RequestEmail
{
get { return (DemandLevel)(ViewState[requestEmailViewStateKey] ?? requestEmailDefault); }
set { ViewState[requestEmailViewStateKey] = value; }
}
const string requestFullNameViewStateKey = "RequestFullName";
const DemandLevel requestFullNameDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's full name from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestFullNameDefault)]
[Description("Your level of interest in receiving the user's full name from the Provider")]
public DemandLevel RequestFullName
{
get { return (DemandLevel)(ViewState[requestFullNameViewStateKey] ?? requestFullNameDefault); }
set { ViewState[requestFullNameViewStateKey] = value; }
}
const string requestBirthDateViewStateKey = "RequestBirthday";
const DemandLevel requestBirthDateDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's birthdate from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestBirthDateDefault)]
[Description("Your level of interest in receiving the user's birthdate from the Provider.")]
public DemandLevel RequestBirthDate
{
get { return (DemandLevel)(ViewState[requestBirthDateViewStateKey] ?? requestBirthDateDefault); }
set { ViewState[requestBirthDateViewStateKey] = value; }
}
const string requestGenderViewStateKey = "RequestGender";
const DemandLevel requestGenderDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's gender from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestGenderDefault)]
[Description("Your level of interest in receiving the user's gender from the Provider.")]
public DemandLevel RequestGender
{
get { return (DemandLevel)(ViewState[requestGenderViewStateKey] ?? requestGenderDefault); }
set { ViewState[requestGenderViewStateKey] = value; }
}
const string requestPostalCodeViewStateKey = "RequestPostalCode";
const DemandLevel requestPostalCodeDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's postal code from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestPostalCodeDefault)]
[Description("Your level of interest in receiving the user's postal code from the Provider.")]
public DemandLevel RequestPostalCode
{
get { return (DemandLevel)(ViewState[requestPostalCodeViewStateKey] ?? requestPostalCodeDefault); }
set { ViewState[requestPostalCodeViewStateKey] = value; }
}
const string requestCountryViewStateKey = "RequestCountry";
const DemandLevel requestCountryDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's country from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestCountryDefault)]
[Description("Your level of interest in receiving the user's country from the Provider.")]
public DemandLevel RequestCountry
{
get { return (DemandLevel)(ViewState[requestCountryViewStateKey] ?? requestCountryDefault); }
set { ViewState[requestCountryViewStateKey] = value; }
}
const string requestLanguageViewStateKey = "RequestLanguage";
const DemandLevel requestLanguageDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's preferred language from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestLanguageDefault)]
[Description("Your level of interest in receiving the user's preferred language from the Provider.")]
public DemandLevel RequestLanguage
{
get { return (DemandLevel)(ViewState[requestLanguageViewStateKey] ?? requestLanguageDefault); }
set { ViewState[requestLanguageViewStateKey] = value; }
}
const string requestTimeZoneViewStateKey = "RequestTimeZone";
const DemandLevel requestTimeZoneDefault = DemandLevel.NoRequest;
///
/// Gets/sets your level of interest in receiving the user's time zone from the Provider.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(requestTimeZoneDefault)]
[Description("Your level of interest in receiving the user's time zone from the Provider.")]
public DemandLevel RequestTimeZone
{
get { return (DemandLevel)(ViewState[requestTimeZoneViewStateKey] ?? requestTimeZoneDefault); }
set { ViewState[requestTimeZoneViewStateKey] = value; }
}
const string policyUrlViewStateKey = "PolicyUrl";
const string policyUrlDefault = "";
///
/// Gets/sets the URL to your privacy policy page that describes how
/// claims will be used and/or shared.
///
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(policyUrlDefault)]
[Description("The URL to your privacy policy page that describes how claims will be used and/or shared.")]
public string PolicyUrl
{
get { return (string)ViewState[policyUrlViewStateKey] ?? policyUrlDefault; }
set {
ValidateResolvableUrl(Page, DesignMode, value);
ViewState[policyUrlViewStateKey] = value;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri")]
internal static void ValidateResolvableUrl(Page page, bool designMode, string value) {
if (string.IsNullOrEmpty(value)) return;
if (page != null && !designMode) {
// Validate new value by trying to construct a Realm object based on it.
new Uri(page.Request.Url, page.ResolveUrl(value)); // throws an exception on failure.
} else {
// We can't fully test it, but it should start with either ~/ or a protocol.
if (Regex.IsMatch(value, @"^https?://")) {
new Uri(value); // make sure it's fully-qualified, but ignore wildcards
} else if (value.StartsWith("~/", StringComparison.Ordinal)) {
// this is valid too
} else
throw new UriFormatException();
}
}
const string enableRequestProfileViewStateKey = "EnableRequestProfile";
const bool enableRequestProfileDefault = true;
///
/// Turns the entire Simple Registration extension on or off.
///
[Bindable(true)]
[Category(profileCategory)]
[DefaultValue(enableRequestProfileDefault)]
[Description("Turns the entire Simple Registration extension on or off.")]
public bool EnableRequestProfile
{
get { return (bool)(ViewState[enableRequestProfileViewStateKey] ?? enableRequestProfileDefault); }
set { ViewState[enableRequestProfileViewStateKey] = value; }
}
#endregion
#region Properties to hide
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override System.Drawing.Color ForeColor
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override System.Drawing.Color BackColor
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override System.Drawing.Color BorderColor
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override Unit BorderWidth
{
get { return Unit.Empty; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override BorderStyle BorderStyle
{
get { return BorderStyle.None; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override FontInfo Font
{
get { return null; }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override Unit Height
{
get { return Unit.Empty; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override Unit Width
{
get { return Unit.Empty; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override string ToolTip
{
get { return string.Empty; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override string SkinID
{
get { return string.Empty; }
set { throw new NotSupportedException(); }
}
///
/// Unused property.
///
[Browsable(false), Bindable(false)]
public override bool EnableTheming
{
get { return false; }
set { throw new NotSupportedException(); }
}
#endregion
///
/// Checks for incoming OpenID authentication responses and fires appropriate events.
///
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (!Enabled || Page.IsPostBack) return;
var consumer = new OpenIdRelyingParty();
if (consumer.Response != null) {
string persistentString = Page.Request.QueryString[usePersistentCookieCallbackKey];
bool persistentBool;
if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) {
UsePersistentCookie = persistentBool;
}
switch (consumer.Response.Status) {
case AuthenticationStatus.Canceled:
OnCanceled(consumer.Response);
break;
case AuthenticationStatus.Authenticated:
OnLoggedIn(consumer.Response);
break;
case AuthenticationStatus.SetupRequired:
OnSetupRequired(consumer.Response);
break;
case AuthenticationStatus.Failed:
OnFailed(consumer.Response);
break;
default:
throw new InvalidOperationException("Unexpected response status code.");
}
}
}
///
/// Prepares the text box to be rendered.
///
protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
if (ShowLogo)
{
string logoUrl = Page.ClientScript.GetWebResourceUrl(
typeof(OpenIdTextBox), EmbeddedLogoResourceName);
WrappedTextBox.Style["background"] = string.Format(CultureInfo.InvariantCulture,
"url({0}) no-repeat", HttpUtility.HtmlEncode(logoUrl));
WrappedTextBox.Style["background-position"] = "0 50%";
WrappedTextBox.Style[HtmlTextWriterStyle.PaddingLeft] = "18px";
WrappedTextBox.Style[HtmlTextWriterStyle.BorderStyle] = "solid";
WrappedTextBox.Style[HtmlTextWriterStyle.BorderWidth] = "1px";
WrappedTextBox.Style[HtmlTextWriterStyle.BorderColor] = "lightgray";
}
}
///
/// The OpenID authentication request that is about to be sent.
///
protected IAuthenticationRequest Request;
///
/// Constructs the authentication request and returns it.
///
///
/// This method need not be called before calling the method,
/// but is offered in the event that adding extensions to the request is desired.
/// The Simple Registration extension arguments are added to the request
/// before returning if is set to true.
///
public IAuthenticationRequest CreateRequest() {
if (Request != null)
throw new InvalidOperationException(Strings.CreateRequestAlreadyCalled);
if (string.IsNullOrEmpty(Text))
throw new InvalidOperationException(DotNetOpenId.Strings.OpenIdTextBoxEmpty);
try {
var consumer = new OpenIdRelyingParty();
// Resolve the trust root, and swap out the scheme and port if necessary to match the
// return_to URL, since this match is required by OpenId, and the consumer app
// may be using HTTP at some times and HTTPS at others.
UriBuilder realm = getResolvedRealm(RealmUrl);
realm.Scheme = Page.Request.Url.Scheme;
realm.Port = Page.Request.Url.Port;
// Initiate openid request
// We use TryParse here to avoid throwing an exception which
// might slip through our validator control if it is disabled.
Identifier userSuppliedIdentifier;
if (Identifier.TryParse(Text, out userSuppliedIdentifier)) {
Request = consumer.CreateRequest(userSuppliedIdentifier, new Realm(realm));
Request.Mode = ImmediateMode ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup;
if (EnableRequestProfile) addProfileArgs(Request);
// Add state that needs to survive across the redirect.
Request.AddCallbackArguments(usePersistentCookieCallbackKey, UsePersistentCookie.ToString(CultureInfo.InvariantCulture));
} else {
Logger.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", Text);
Request = null;
}
} catch (WebException ex) {
OnFailed(new FailedAuthenticationResponse(ex));
} catch (OpenIdException ex) {
OnFailed(new FailedAuthenticationResponse(ex));
}
return Request;
}
///
/// Immediately redirects to the OpenID Provider to verify the Identifier
/// provided in the text box.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
public void LogOn()
{
if (Request == null)
CreateRequest();
if (Request != null)
Request.RedirectToProvider();
}
void addProfileArgs(IAuthenticationRequest request)
{
request.AddExtension(new ClaimsRequest() {
Nickname = RequestNickname,
Email = RequestEmail,
FullName = RequestFullName,
BirthDate = RequestBirthDate,
Gender = RequestGender,
PostalCode = RequestPostalCode,
Country = RequestCountry,
Language = RequestLanguage,
TimeZone = RequestTimeZone,
PolicyUrl = string.IsNullOrEmpty(PolicyUrl) ?
null : new Uri(Util.GetRequestUrlFromContext(), Page.ResolveUrl(PolicyUrl)),
});
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenId.Realm")]
UriBuilder getResolvedRealm(string realm)
{
Debug.Assert(Page != null, "Current HttpContext required to resolve URLs.");
// Allow for *. realm notation, as well as ASP.NET ~/ shortcuts.
// We have to temporarily remove the *. notation if it's there so that
// the rest of our URL manipulation will succeed.
bool foundWildcard = false;
// Note: we don't just use string.Replace because poorly written URLs
// could potentially have multiple :// sequences in them.
string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.",
delegate(Match m) {
foundWildcard = true;
return m.Groups[1].Value;
});
UriBuilder fullyQualifiedRealm = new UriBuilder(
new Uri(Util.GetRequestUrlFromContext(), Page.ResolveUrl(realmNoWildcard)));
if (foundWildcard)
{
fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host;
}
// Is it valid?
new Realm(fullyQualifiedRealm); // throws if not valid
return fullyQualifiedRealm;
}
#region Events
///
/// Fired upon completion of a successful login.
///
[Description("Fired upon completion of a successful login.")]
public event EventHandler LoggedIn;
///
/// Fires the event.
///
protected virtual void OnLoggedIn(IAuthenticationResponse response)
{
if (response == null) throw new ArgumentNullException("response");
Debug.Assert(response.Status == AuthenticationStatus.Authenticated);
var loggedIn = LoggedIn;
OpenIdEventArgs args = new OpenIdEventArgs(response);
if (loggedIn != null)
loggedIn(this, args);
if (!args.Cancel)
FormsAuthentication.RedirectFromLoginPage(
response.ClaimedIdentifier.ToString(), UsePersistentCookie);
}
///
/// Fired when a login attempt fails.
///
[Description("Fired when a login attempt fails.")]
public event EventHandler Failed;
///
/// Fires the event.
///
protected virtual void OnFailed(IAuthenticationResponse response)
{
if (response == null) throw new ArgumentNullException("response");
Debug.Assert(response.Status == AuthenticationStatus.Failed);
var failed = Failed;
if (failed != null)
failed(this, new OpenIdEventArgs(response));
}
///
/// Fired when an authentication attempt is canceled at the OpenID Provider.
///
[Description("Fired when an authentication attempt is canceled at the OpenID Provider.")]
public event EventHandler Canceled;
///
/// Fires the event.
///
protected virtual void OnCanceled(IAuthenticationResponse response)
{
if (response == null) throw new ArgumentNullException("response");
Debug.Assert(response.Status == AuthenticationStatus.Canceled);
var canceled = Canceled;
if (canceled != null)
canceled(this, new OpenIdEventArgs(response));
}
///
/// Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.
///
[Description("Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.")]
public event EventHandler SetupRequired;
///
/// Fires the event.
///
protected virtual void OnSetupRequired(IAuthenticationResponse response) {
if (response == null) throw new ArgumentNullException("response");
Debug.Assert(response.Status == AuthenticationStatus.SetupRequired);
// Why are we firing Failed when we're OnSetupRequired? Backward compatibility.
var setupRequired = SetupRequired;
if (setupRequired != null)
setupRequired(this, new OpenIdEventArgs(response));
}
#endregion
}
///
/// The event details passed to event handlers.
///
public class OpenIdEventArgs : EventArgs {
///
/// Constructs an object with minimal information of an incomplete or failed
/// authentication attempt.
///
internal OpenIdEventArgs(IAuthenticationRequest request) {
if (request == null) throw new ArgumentNullException("request");
Request = request;
ClaimedIdentifier = request.ClaimedIdentifier;
IsDirectedIdentity = request.IsDirectedIdentity;
}
///
/// Constructs an object with information on a completed authentication attempt
/// (whether that attempt was successful or not).
///
internal OpenIdEventArgs(IAuthenticationResponse response) {
if (response == null) throw new ArgumentNullException("response");
Response = response;
ClaimedIdentifier = response.ClaimedIdentifier;
ProfileFields = response.GetExtension();
}
///
/// Cancels the OpenID authentication and/or login process.
///
public bool Cancel { get; set; }
///
/// The Identifier the user is claiming to own. Or null if the user
/// is using Directed Identity.
///
public Identifier ClaimedIdentifier { get; private set; }
///
/// Whether the user has selected to let his Provider determine
/// the ClaimedIdentifier to use as part of successful authentication.
///
public bool IsDirectedIdentity { get; private set; }
///
/// Gets the details of the OpenID authentication request,
/// and allows for adding extensions.
///
public IAuthenticationRequest Request { get; private set; }
///
/// Gets the details of the OpenID authentication response.
///
public IAuthenticationResponse Response { get; private set; }
///
/// Gets the simple registration (sreg) extension fields given
/// by the provider, if any.
///
public ClaimsResponse ProfileFields { get; private set; }
}
}