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