//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId.RelyingParty {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Script.Serialization;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OpenId.Extensions;
using DotNetOpenAuth.OpenId.Extensions.UI;
using Validation;
///
/// Provides the programmatic facilities to act as an AJAX-enabled OpenID relying party.
///
public class OpenIdAjaxRelyingParty : OpenIdRelyingParty {
///
/// Initializes a new instance of the class.
///
public OpenIdAjaxRelyingParty() {
Reporting.RecordFeatureUse(this);
}
///
/// Initializes a new instance of the class.
///
/// The application store. If null, the relying party will always operate in "dumb mode".
public OpenIdAjaxRelyingParty(ICryptoKeyAndNonceStore applicationStore)
: base(applicationStore) {
Reporting.RecordFeatureUse(this);
}
///
/// Generates AJAX-ready authentication requests that can satisfy the requirements of some OpenID Identifier.
///
/// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
/// The shorest URL that describes this relying party web site's address.
/// For example, if your login page is found at https://www.example.com/login.aspx,
/// your realm would typically be https://www.example.com/.
/// The URL of the login page, or the page prepared to receive authentication
/// responses from the OpenID Provider.
/// The cancellation token.
///
/// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
/// Never null, but may be empty.
///
///
/// Any individual generated request can satisfy the authentication.
/// The generated requests are sorted in preferred order.
/// Each request is generated as it is enumerated to. Associations are created only as
/// is called.
/// No exception is thrown if no OpenID endpoints were discovered.
/// An empty enumerable is returned instead.
///
public override async Task> CreateRequestsAsync(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl, CancellationToken cancellationToken) {
var requests = await base.CreateRequestsAsync(userSuppliedIdentifier, realm, returnToUrl, cancellationToken);
var results = new List();
// Alter the requests so that have AJAX characteristics.
// Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example).
// Since we're gathering OPs to try one after the other, just take the first choice of each OP
// and don't try it multiple times.
requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance);
// Configure each generated request.
int reqIndex = 0;
foreach (var req in requests) {
// Inform ourselves in return_to that we're in a popup.
req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.UIPopupCallbackKey, "1");
if (req.DiscoveryResult.IsExtensionSupported()) {
// Inform the OP that we'll be using a popup window consistent with the UI extension.
req.AddExtension(new UIRequest());
// Provide a hint for the client javascript about whether the OP supports the UI extension.
// This is so the window can be made the correct size for the extension.
// If the OP doesn't advertise support for the extension, the javascript will use
// a bigger popup window.
req.SetUntrustedCallbackArgument(OpenIdRelyingPartyControlBase.PopupUISupportedJSHint, "1");
}
req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture));
// If the ReturnToUrl was explicitly set, we'll need to reset our first parameter
if (OpenIdElement.Configuration.RelyingParty.PreserveUserSuppliedIdentifier) {
if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) {
req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString);
}
}
// Our javascript needs to let the user know which endpoint responded. So we force it here.
// This gives us the info even for 1.0 OPs and 2.0 setup_required responses.
req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.OPEndpointParameterName, req.Provider.Uri.AbsoluteUri);
req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty);
// Inform ourselves in return_to that we're in a popup or iframe.
req.SetUntrustedCallbackArgument(OpenIdRelyingPartyAjaxControlBase.UIPopupCallbackKey, "1");
// We append a # at the end so that if the OP happens to support it,
// the OpenID response "query string" is appended after the hash rather than before, resulting in the
// browser being super-speedy in closing the popup window since it doesn't try to pull a newer version
// of the static resource down from the server merely because of a changed URL.
// http://www.nabble.com/Re:-Defining-how-OpenID-should-behave-with-fragments-in-the-return_to-url-p22694227.html
////TODO:
results.Add(req);
}
return results;
}
///
/// Serializes discovery results on some single identifier on behalf of Javascript running on the browser.
///
/// The discovery results from just one identifier to serialize as a JSON response.
/// The cancellation token.
///
/// The JSON result to return to the user agent.
///
///
/// We prepare a JSON object with this interface:
///
/// class jsonResponse {
/// string claimedIdentifier;
/// Array requests; // never null
/// string error; // null if no error
/// }
///
/// Each element in the requests array looks like this:
///
/// class jsonAuthRequest {
/// string endpoint; // URL to the OP endpoint
/// string immediate; // URL to initiate an immediate request
/// string setup; // URL to initiate a setup request.
/// }
///
///
public async Task AsAjaxDiscoveryResultAsync(IEnumerable requests, CancellationToken cancellationToken) {
Requires.NotNull(requests, "requests");
var serializer = new JavaScriptSerializer();
var response = new HttpResponseMessage {
Content = new StringContent(serializer.Serialize(await this.AsJsonDiscoveryResultAsync(requests, cancellationToken))),
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
}
///
/// Serializes discovery on a set of identifiers for preloading into an HTML page that carries
/// an AJAX-aware OpenID control.
///
/// The discovery results to serialize as a JSON response.
/// The cancellation token.
///
/// The JSON result to return to the user agent.
///
public async Task AsAjaxPreloadedDiscoveryResultAsync(IEnumerable requests, CancellationToken cancellationToken) {
Requires.NotNull(requests, "requests");
var serializer = new JavaScriptSerializer();
string json = serializer.Serialize(await this.AsJsonPreloadedDiscoveryResultAsync(requests, cancellationToken));
string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + json + ");";
return script;
}
///
/// Converts a sequence of authentication requests to a JSON object for seeding an AJAX-enabled login page.
///
/// The discovery results from just one identifier to serialize as a JSON response.
/// The cancellation token.
/// A JSON object, not yet serialized.
internal async Task