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