//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenIdOfflineProvider {
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.Provider;
using log4net;
///
/// The OpenID Provider host.
///
internal class HostedProvider : IDisposable {
///
/// The path to the Provider Endpoint.
///
private const string ProviderPath = "/provider";
///
/// The path to the OP Identifier.
///
private const string OPIdentifierPath = "/";
///
/// The URL path with which all user identities must start.
///
private const string UserIdentifierPath = "/user/";
///
/// The instance that processes incoming requests.
///
private OpenIdProvider provider = new OpenIdProvider(new StandardProviderApplicationStore());
///
/// The HTTP listener that acts as the OpenID Provider socket.
///
private HttpHost httpHost;
///
/// Initializes a new instance of the class.
///
internal HostedProvider() {
}
///
/// Gets a value indicating whether this instance is running.
///
///
/// true if this instance is running; otherwise, false.
///
internal bool IsRunning {
get { return this.httpHost != null; }
}
///
/// Gets the instance that processes incoming requests.
///
internal OpenIdProvider Provider {
get { return this.provider; }
}
///
/// Gets or sets the delegate that handles authentication requests.
///
internal Action ProcessRequest { get; set; }
///
/// Gets the provider endpoint.
///
internal Uri ProviderEndpoint {
get {
Contract.Requires(this.IsRunning);
return new Uri(this.httpHost.BaseUri, ProviderPath);
}
}
///
/// Gets the base URI that all user identities must start with.
///
internal Uri UserIdentityPageBase {
get {
Contract.Requires(this.IsRunning);
return new Uri(this.httpHost.BaseUri, UserIdentifierPath);
}
}
///
/// Gets the OP identifier.
///
internal Uri OPIdentifier {
get {
Contract.Requires(this.IsRunning);
return new Uri(this.httpHost.BaseUri, OPIdentifierPath);
}
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
this.Dispose(true);
}
///
/// Starts the provider.
///
internal void StartProvider() {
Contract.Ensures(this.IsRunning);
this.httpHost = HttpHost.CreateHost(this.RequestHandler);
}
///
/// Stops the provider.
///
internal void StopProvider() {
Contract.Ensures(!this.IsRunning);
if (this.httpHost != null) {
this.httpHost.Dispose();
this.httpHost = null;
}
}
#region IDisposable Members
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing) {
if (disposing) {
var host = this.httpHost as IDisposable;
if (host != null) {
host.Dispose();
}
this.httpHost = null;
}
}
#endregion
///
/// Generates HTML for an identity page.
///
/// The provider endpoint.
/// The local id.
/// The HTML document to return to the RP.
private static string GenerateHtmlDiscoveryDocument(Uri providerEndpoint, string localId) {
Contract.Requires(providerEndpoint != null);
const string DelegatedHtmlDiscoveryFormat = @"
";
const string NonDelegatedHtmlDiscoveryFormat = @"
";
return string.Format(
localId != null ? DelegatedHtmlDiscoveryFormat : NonDelegatedHtmlDiscoveryFormat,
providerEndpoint.AbsoluteUri,
localId);
}
///
/// Generates the OP Identifier XRDS document.
///
/// The provider endpoint.
/// The supported extensions.
/// The content of the XRDS document.
private static string GenerateXrdsOPIdentifierDocument(Uri providerEndpoint, IEnumerable supportedExtensions) {
Contract.Requires(providerEndpoint != null);
Contract.Requires(supportedExtensions != null);
const string OPIdentifierDiscoveryFormat = @"
http://specs.openid.net/auth/2.0/server
{1}
{0}
";
string extensions = string.Join(
"\n\t\t\t",
supportedExtensions.Select(ext => "" + ext + "").ToArray());
return string.Format(
OPIdentifierDiscoveryFormat,
providerEndpoint.AbsoluteUri,
extensions);
}
///
/// Handles incoming HTTP requests.
///
/// The HttpListener context.
private void RequestHandler(HttpListenerContext context) {
Contract.Requires(context != null);
Contract.Requires(context.Response.OutputStream != null);
Contract.Requires(this.ProcessRequest != null);
Stream outputStream = context.Response.OutputStream;
Contract.Assume(outputStream != null); // CC static verification shortcoming.
UriBuilder providerEndpointBuilder = new UriBuilder();
providerEndpointBuilder.Scheme = Uri.UriSchemeHttp;
providerEndpointBuilder.Host = "localhost";
providerEndpointBuilder.Port = context.Request.Url.Port;
providerEndpointBuilder.Path = ProviderPath;
Uri providerEndpoint = providerEndpointBuilder.Uri;
if (context.Request.Url.AbsolutePath == ProviderPath) {
HttpRequestBase requestInfo = HttpRequestInfo.Create(context.Request);
this.ProcessRequest(requestInfo, context.Response);
} else if (context.Request.Url.AbsolutePath.StartsWith(UserIdentifierPath, StringComparison.Ordinal)) {
using (StreamWriter sw = new StreamWriter(outputStream)) {
context.Response.StatusCode = (int)HttpStatusCode.OK;
string localId = null; // string.Format("http://localhost:{0}/user", context.Request.Url.Port);
string html = GenerateHtmlDiscoveryDocument(providerEndpoint, localId);
sw.WriteLine(html);
}
context.Response.OutputStream.Close();
} else if (context.Request.Url == this.OPIdentifier) {
context.Response.ContentType = "application/xrds+xml";
context.Response.StatusCode = (int)HttpStatusCode.OK;
App.Logger.Info("Discovery on OP Identifier detected.");
using (StreamWriter sw = new StreamWriter(outputStream)) {
sw.Write(GenerateXrdsOPIdentifierDocument(providerEndpoint, Enumerable.Empty()));
}
context.Response.OutputStream.Close();
} else {
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.OutputStream.Close();
}
}
}
}