//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; using System.ComponentModel; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; using Validation; /// /// An OpenID Provider control that automatically responds to certain /// automated OpenID messages, and routes authentication requests to /// custom code via an event handler. /// [DefaultEvent("AuthenticationChallenge")] [ToolboxData("<{0}:ProviderEndpoint runat='server' />")] public class ProviderEndpoint : Control { /// /// The key used to store the pending authentication request in the ASP.NET session. /// private const string PendingRequestKey = "pendingRequest"; /// /// The default value for the property. /// private const bool EnabledDefault = true; /// /// The view state key in which to store the value of the property. /// private const string EnabledViewStateKey = "Enabled"; /// /// Backing field for the property. /// private Lazy provider; /// /// Initializes a new instance of the class. /// public ProviderEndpoint() { this.provider = new Lazy(this.CreateProvider); } /// /// Fired when an incoming OpenID request is an authentication challenge /// that must be responded to by the Provider web site according to its /// own user database and policies. /// public event EventHandler AuthenticationChallenge; /// /// Fired when an incoming OpenID message carries extension requests /// but is not regarding any OpenID identifier. /// public event EventHandler AnonymousRequest; /// /// Gets or sets an incoming OpenID authentication request that has not yet been responded to. /// /// /// This request is stored in the ASP.NET Session state, so it will survive across /// redirects, postbacks, and transfers. This allows you to authenticate the user /// yourself, and confirm his/her desire to authenticate to the relying party site /// before responding to the relying party's authentication request. /// public static IAuthenticationRequest PendingAuthenticationRequest { get { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest; } set { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); HttpContext.Current.Session[PendingRequestKey] = value; } } /// /// Gets or sets an incoming OpenID anonymous request that has not yet been responded to. /// /// /// This request is stored in the ASP.NET Session state, so it will survive across /// redirects, postbacks, and transfers. This allows you to authenticate the user /// yourself, and confirm his/her desire to provide data to the relying party site /// before responding to the relying party's request. /// public static IAnonymousRequest PendingAnonymousRequest { get { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest; } set { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); HttpContext.Current.Session[PendingRequestKey] = value; } } /// /// Gets or sets an incoming OpenID request that has not yet been responded to. /// /// /// This request is stored in the ASP.NET Session state, so it will survive across /// redirects, postbacks, and transfers. This allows you to authenticate the user /// yourself, and confirm his/her desire to provide data to the relying party site /// before responding to the relying party's request. /// public static IHostProcessedRequest PendingRequest { get { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; } set { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); RequiresEx.ValidState(HttpContext.Current.Session != null, MessagingStrings.SessionRequired); HttpContext.Current.Session[PendingRequestKey] = value; } } /// /// Gets or sets the instance to use for all instances of this control. /// /// The default value is an instance initialized according to the web.config file. public OpenIdProvider Provider { get { return this.provider.Value; } set { Requires.NotNull(value, "value"); this.provider = new Lazy(() => value, LazyThreadSafetyMode.PublicationOnly); } } /// /// Gets or sets a value indicating whether or not this control should /// be listening for and responding to incoming OpenID requests. /// [Category("Behavior"), DefaultValue(EnabledDefault)] public bool Enabled { get { return ViewState[EnabledViewStateKey] == null ? EnabledDefault : (bool)ViewState[EnabledViewStateKey]; } set { ViewState[EnabledViewStateKey] = value; } } /// /// Sends the response for the and clears the property. /// /// The cancellation token. /// /// The response message. /// public Task PrepareResponseAsync(CancellationToken cancellationToken = default(CancellationToken)) { var pendingRequest = PendingRequest; PendingRequest = null; return this.Provider.PrepareResponseAsync(pendingRequest, cancellationToken); } /// /// Checks for incoming OpenID requests, responds to ones it can /// respond to without policy checks, and fires events for custom /// handling of the ones it cannot decide on automatically. /// /// The object that contains the event data. protected override void OnLoad(EventArgs e) { base.OnLoad(e); this.Page.RegisterAsyncTask(new PageAsyncTask(async cancellationToken => { // There is the unusual scenario that this control is hosted by // an ASP.NET web page that has other UI on it to that the user // might see, including controls that cause a postback to occur. // We definitely want to ignore postbacks, since any openid messages // they contain will be old. if (this.Enabled && !this.Page.IsPostBack) { // Use the explicitly given state store on this control if there is one. // Then try the configuration file specified one. Finally, use the default // in-memory one that's built into OpenIdProvider. // determine what incoming message was received IRequest request = await Provider.GetRequestAsync(new HttpRequestWrapper(this.Context.Request), cancellationToken); if (request != null) { PendingRequest = null; // process the incoming message appropriately and send the response IAuthenticationRequest idrequest; IAnonymousRequest anonRequest; if ((idrequest = request as IAuthenticationRequest) != null) { PendingAuthenticationRequest = idrequest; this.OnAuthenticationChallenge(idrequest); } else if ((anonRequest = request as IAnonymousRequest) != null) { PendingAnonymousRequest = anonRequest; if (!this.OnAnonymousRequest(anonRequest)) { // This is a feature not supported by the OP, so // go ahead and set disapproved so we can send a response. Logger.OpenId.Warn( "An incoming anonymous OpenID request message was detected, but the ProviderEndpoint.AnonymousRequest event is not handled, so returning cancellation message to relying party."); anonRequest.IsApproved = false; } } if (request.IsResponseReady) { PendingAuthenticationRequest = null; var response = await Provider.PrepareResponseAsync(request, cancellationToken); await response.SendAsync(new HttpContextWrapper(this.Context), cancellationToken); this.Context.Response.End(); } } } })); } /// /// Fires the event. /// /// The request to include in the event args. protected virtual void OnAuthenticationChallenge(IAuthenticationRequest request) { var authenticationChallenge = this.AuthenticationChallenge; if (authenticationChallenge != null) { authenticationChallenge(this, new AuthenticationChallengeEventArgs(request)); } } /// /// Fires the event. /// /// The request to include in the event args. /// true if there were any anonymous request handlers. protected virtual bool OnAnonymousRequest(IAnonymousRequest request) { var anonymousRequest = this.AnonymousRequest; if (anonymousRequest != null) { anonymousRequest(this, new AnonymousRequestEventArgs(request)); return true; } else { return false; } } /// /// Creates the default OpenIdProvider to use. /// /// The new instance of OpenIdProvider. private OpenIdProvider CreateProvider() { return new OpenIdProvider(OpenIdElement.Configuration.Provider.ApplicationStore.CreateInstance(OpenIdProvider.GetHttpApplicationStore(new HttpContextWrapper(this.Context)), null)); } } }