summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-01-29 14:32:45 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-01-29 14:32:45 -0800
commit5fec515095ee10b522f414a03e78f282aaf520dc (patch)
tree204c75486639c23cdda2ef38b34d7e5050a1a2e3 /src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
parentf1a4155398635a4fd9f485eec817152627682704 (diff)
parent8f4165ee515728aca3faaa26e8354a40612e85e4 (diff)
downloadDotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.zip
DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.gz
DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.bz2
Merge branch 'splitDlls'.
DNOA now builds and (in some cases) ships as many distinct assemblies.
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js')
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js751
1 files changed, 751 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
new file mode 100644
index 0000000..4de5188
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty.UI/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js
@@ -0,0 +1,751 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdRelyingPartyAjaxControlBase.js" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// This file may be used and redistributed under the terms of the
+// Microsoft Public License (Ms-PL) http://opensource.org/licenses/ms-pl.html
+// </copyright>
+//-----------------------------------------------------------------------
+
+if (window.dnoa_internal === undefined) {
+ window.dnoa_internal = {};
+}
+
+/// <summary>Removes a given element from the array.</summary>
+/// <returns>True if the element was in the array, or false if it was not found.</returns>
+Array.prototype.remove = function(element) {
+ function elementToRemoveLast(a, b) {
+ if (a == element) { return 1; }
+ if (b == element) { return -1; }
+ return 0;
+ }
+ this.sort(elementToRemoveLast);
+ if (this[this.length - 1] == element) {
+ this.pop();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Renders all the parameters in their string form, surrounded by parentheses.
+window.dnoa_internal.argsToString = function() {
+ result = "(";
+ for (var i = 0; i < arguments.length; i++) {
+ if (i > 0) { result += ', '; }
+ var arg = arguments[i];
+ if (typeof (arg) == 'string') {
+ arg = '"' + arg + '"';
+ } else if (arg === null) {
+ arg = '[null]';
+ } else if (arg === undefined) {
+ arg = '[undefined]';
+ }
+ result += arg.toString();
+ }
+ result += ')';
+ return result;
+};
+
+window.dnoa_internal.registerEvent = function(name) {
+ var filterOnApplicability = function(fn, domElement) {
+ /// <summary>Wraps a given function with a check so that the function only executes when a given element is still in the DOM.</summary>
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ if (!domElement) {
+ // no element used as a basis of applicability indicates we always fire this callback.
+ fn.apply(null, args);
+ } else {
+ var elements = document.getElementsByTagName(domElement.tagName);
+ var isElementInDom = false;
+ for (var i = 0; i < elements.length; i++) {
+ if (elements[i] === domElement) {
+ isElementInDom = true;
+ break;
+ }
+ }
+ if (isElementInDom) {
+ fn.apply(null, args);
+ }
+ }
+ }
+ };
+
+ window.dnoa_internal[name + 'Listeners'] = [];
+ window.dnoa_internal['add' + name] = function(fn, whileDomElementApplicable) { window.dnoa_internal[name + 'Listeners'].push(filterOnApplicability(fn, whileDomElementApplicable)); };
+ window.dnoa_internal['remove' + name] = function(fn) { window.dnoa_internal[name + 'Listeners'].remove(fn); };
+ window.dnoa_internal['fire' + name] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ trace('Firing event ' + name + window.dnoa_internal.argsToString.apply(null, args), 'blue');
+ var listeners = window.dnoa_internal[name + 'Listeners'];
+ for (var i = 0; i < listeners.length; i++) {
+ listeners[i].apply(null, args);
+ }
+ };
+};
+
+window.dnoa_internal.registerEvent('DiscoveryStarted'); // (identifier) - fired when a discovery callback is ACTUALLY made to the RP
+window.dnoa_internal.registerEvent('DiscoverySuccess'); // (identifier, discoveryResult, { fresh: true|false }) - fired after a discovery callback is returned from the RP successfully or a cached result is retrieved
+window.dnoa_internal.registerEvent('DiscoveryFailed'); // (identifier, message) - fired after a discovery callback fails
+window.dnoa_internal.registerEvent('AuthStarted'); // (discoveryResult, serviceEndpoint, { background: true|false })
+window.dnoa_internal.registerEvent('AuthFailed'); // (discoveryResult, serviceEndpoint, { background: true|false }) - fired for each individual ServiceEndpoint, and once at last with serviceEndpoint==null if all failed
+window.dnoa_internal.registerEvent('AuthSuccess'); // (discoveryResult, serviceEndpoint, extensionResponses, { background: true|false, deserialized: true|false })
+window.dnoa_internal.registerEvent('AuthCleared'); // (discoveryResult, serviceEndpoint)
+
+window.dnoa_internal.discoveryResults = []; // user supplied identifiers and discovery results
+window.dnoa_internal.discoveryInProgress = []; // identifiers currently being discovered and their callbacks
+
+// The possible authentication results
+window.dnoa_internal.authSuccess = 'auth-success';
+window.dnoa_internal.authRefused = 'auth-refused';
+window.dnoa_internal.timedOut = 'timed-out';
+
+/// <summary>Instantiates a new FrameManager.</summary>
+/// <param name="maxFrames">The maximum number of concurrent 'jobs' (authentication attempts).</param>
+window.dnoa_internal.FrameManager = function(maxFrames) {
+ this.queuedWork = [];
+ this.frames = [];
+ this.maxFrames = maxFrames;
+
+ /// <summary>Called to queue up some work that will use an iframe as soon as it is available.</summary>
+ /// <param name="job">
+ /// A delegate that must return { url: /*to point the iframe to*/, onCanceled: /* callback */ }
+ /// Its first parameter is the iframe created to service the request.
+ /// It will only be called when the work actually begins.
+ /// </param>
+ /// <param name="p1">Arbitrary additional parameter to pass to the job.</param>
+ this.enqueueWork = function(job, p1) {
+ // Assign an iframe to this task immediately if there is one available.
+ if (this.frames.length < this.maxFrames) {
+ this.createIFrame(job, p1);
+ } else {
+ this.queuedWork.unshift({ job: job, p1: p1 });
+ }
+ };
+
+ /// <summary>Clears the job queue and immediately closes all iframes.</summary>
+ this.cancelAllWork = function() {
+ trace('Canceling all open and pending iframes.');
+ while (this.queuedWork.pop()) { }
+ this.closeFrames();
+ };
+
+ /// <summary>An event fired when a frame is closing.</summary>
+ this.onJobCompleted = function() {
+ // If there is a job in the queue, go ahead and start it up.
+ if (jobDesc = this.queuedWork.pop()) {
+ this.createIFrame(jobDesc.job, jobDesc.p1);
+ }
+ };
+
+ this.createIFrame = function(job, p1) {
+ var iframe = document.createElement("iframe");
+ if (!window.openid_visible_iframe) {
+ iframe.setAttribute("width", 0);
+ iframe.setAttribute("height", 0);
+ iframe.setAttribute("style", "display: none");
+ }
+ var jobDescription = job(iframe, p1);
+ iframe.setAttribute("src", jobDescription.url);
+ iframe.onCanceled = jobDescription.onCanceled;
+ iframe.dnoa_internal = window.dnoa_internal;
+ document.body.insertBefore(iframe, document.body.firstChild);
+ this.frames.push(iframe);
+ return iframe;
+ };
+
+ this.closeFrames = function() {
+ if (this.frames.length === 0) { return false; }
+ for (var i = 0; i < this.frames.length; i++) {
+ this.frames[i].src = "about:blank"; // doesn't have to exist. Just stop its processing.
+ if (this.frames[i].parentNode) { this.frames[i].parentNode.removeChild(this.frames[i]); }
+ }
+ while (this.frames.length > 0) {
+ var frame = this.frames.pop();
+ if (frame.onCanceled) { frame.onCanceled(); }
+ }
+ return true;
+ };
+
+ this.closeFrame = function(frame) {
+ frame.src = "about:blank"; // doesn't have to exist. Just stop its processing.
+ if (frame.parentNode) { frame.parentNode.removeChild(frame); }
+ var removed = this.frames.remove(frame);
+ this.onJobCompleted();
+ return removed;
+ };
+};
+
+/// <summary>Instantiates an object that represents an OpenID Identifier.</summary>
+window.OpenIdIdentifier = function(identifier) {
+ if (!identifier || identifier.length === 0) {
+ throw 'Error: trying to create OpenIdIdentifier for null or empty string.';
+ }
+
+ /// <summary>Performs discovery on the identifier.</summary>
+ /// <param name="onDiscoverSuccess">A function(DiscoveryResult) callback to be called when discovery has completed successfully.</param>
+ /// <param name="onDiscoverFailure">A function callback to be called when discovery has completed in failure.</param>
+ this.discover = function(onDiscoverSuccess, onDiscoverFailure) {
+ /// <summary>Receives the results of a successful discovery (even if it yielded 0 results).</summary>
+ function discoverSuccessCallback(discoveryResult, identifier) {
+ trace('Discovery completed for: ' + identifier);
+
+ // Deserialize the JSON object and store the result if it was a successful discovery.
+ discoveryResult = eval('(' + discoveryResult + ')');
+
+ // Add behavior for later use.
+ discoveryResult = new window.dnoa_internal.DiscoveryResult(identifier, discoveryResult);
+ window.dnoa_internal.discoveryResults[identifier] = discoveryResult;
+
+ window.dnoa_internal.fireDiscoverySuccess(identifier, discoveryResult, { fresh: true });
+
+ // Clear our "in discovery" state and fire callbacks
+ var callbacks = window.dnoa_internal.discoveryInProgress[identifier];
+ window.dnoa_internal.discoveryInProgress[identifier] = null;
+
+ if (callbacks) {
+ for (var i = 0; i < callbacks.onSuccess.length; i++) {
+ if (callbacks.onSuccess[i]) {
+ callbacks.onSuccess[i](discoveryResult);
+ }
+ }
+ }
+ }
+
+ /// <summary>Receives the discovery failure notification.</summary>
+ function discoverFailureCallback(message, userSuppliedIdentifier) {
+ trace('Discovery failed for: ' + identifier);
+
+ // Clear our "in discovery" state and fire callbacks
+ var callbacks = window.dnoa_internal.discoveryInProgress[identifier];
+ window.dnoa_internal.discoveryInProgress[identifier] = null;
+
+ if (callbacks) {
+ for (var i = 0; i < callbacks.onSuccess.length; i++) {
+ if (callbacks.onFailure[i]) {
+ callbacks.onFailure[i](message);
+ }
+ }
+ }
+
+ window.dnoa_internal.fireDiscoveryFailed(identifier, message);
+ }
+
+ if (window.dnoa_internal.discoveryResults[identifier]) {
+ trace("We've already discovered " + identifier + " so we're using the cached version.");
+
+ // In this special case, we never fire the DiscoveryStarted event.
+ window.dnoa_internal.fireDiscoverySuccess(identifier, window.dnoa_internal.discoveryResults[identifier], { fresh: false });
+
+ if (onDiscoverSuccess) {
+ onDiscoverSuccess(window.dnoa_internal.discoveryResults[identifier]);
+ }
+
+ return;
+ }
+
+ window.dnoa_internal.fireDiscoveryStarted(identifier);
+
+ if (!window.dnoa_internal.discoveryInProgress[identifier]) {
+ trace('starting discovery on ' + identifier);
+ window.dnoa_internal.discoveryInProgress[identifier] = {
+ onSuccess: [onDiscoverSuccess],
+ onFailure: [onDiscoverFailure]
+ };
+ window.dnoa_internal.callbackAsync(identifier, discoverSuccessCallback, discoverFailureCallback);
+ } else {
+ trace('Discovery on ' + identifier + ' already started. Registering an additional callback.');
+ window.dnoa_internal.discoveryInProgress[identifier].onSuccess.push(onDiscoverSuccess);
+ window.dnoa_internal.discoveryInProgress[identifier].onFailure.push(onDiscoverFailure);
+ }
+ };
+
+ /// <summary>Performs discovery and immediately begins checkid_setup to authenticate the user using a given identifier.</summary>
+ this.login = function(onSuccess, onLoginFailure) {
+ this.discover(function(discoveryResult) {
+ if (discoveryResult) {
+ trace('Discovery succeeded and found ' + discoveryResult.length + ' OpenID service endpoints.');
+ if (discoveryResult.length > 0) {
+ discoveryResult[0].loginPopup(onSuccess, onLoginFailure);
+ } else {
+ trace("This doesn't look like an OpenID Identifier. Aborting login.");
+ if (onLoginFailure) {
+ onLoginFailure();
+ }
+ }
+ }
+ });
+ };
+
+ /// <summary>Performs discovery and immediately begins checkid_immediate on all discovered endpoints.</summary>
+ this.loginBackground = function(frameManager, onLoginSuccess, onLoginFailure, timeout, onLoginLastFailure) {
+ this.discover(function(discoveryResult) {
+ if (discoveryResult) {
+ trace('Discovery succeeded and found ' + discoveryResult.length + ' OpenID service endpoints.');
+ if (discoveryResult.length > 0) {
+ discoveryResult.loginBackground(frameManager, onLoginSuccess, onLoginFailure, onLoginLastFailure || onLoginFailure, timeout);
+ } else {
+ trace("This doesn't look like an OpenID Identifier. Aborting login.");
+ if (onLoginFailure) {
+ onLoginFailure();
+ }
+ }
+ }
+ });
+ };
+
+ this.toString = function() {
+ return identifier;
+ };
+};
+
+/// <summary>Invoked by RP web server when an authentication has completed.</summary>
+/// <remarks>The duty of this method is to distribute the notification to the appropriate tracking object.</remarks>
+window.dnoa_internal.processAuthorizationResult = function(resultUrl, extensionResponses) {
+ //trace('processAuthorizationResult ' + resultUrl);
+ var resultUri = new window.dnoa_internal.Uri(resultUrl);
+ trace('processing auth result with extensionResponses: ' + extensionResponses);
+ if (extensionResponses) {
+ extensionResponses = eval(extensionResponses);
+ }
+
+ // Find the tracking object responsible for this request.
+ var userSuppliedIdentifier = resultUri.getQueryArgValue('dnoa.userSuppliedIdentifier');
+ if (!userSuppliedIdentifier) {
+ throw 'processAuthorizationResult called but no userSuppliedIdentifier parameter was found. Exiting function.';
+ }
+ var discoveryResult = window.dnoa_internal.discoveryResults[userSuppliedIdentifier];
+ if (!discoveryResult) {
+ throw 'processAuthorizationResult called but no discovery result matching user supplied identifier ' + userSuppliedIdentifier + ' was found. Exiting function.';
+ }
+
+ var opEndpoint = resultUri.getQueryArgValue("openid.op_endpoint") ? resultUri.getQueryArgValue("openid.op_endpoint") : resultUri.getQueryArgValue("dnoa.op_endpoint");
+ var respondingEndpoint = discoveryResult.findByEndpoint(opEndpoint);
+ trace('Auth result for ' + respondingEndpoint.host + ' received.'); //: ' + resultUrl);
+
+ if (window.dnoa_internal.isAuthSuccessful(resultUri)) {
+ discoveryResult.successAuthData = resultUrl;
+ respondingEndpoint.onAuthSuccess(resultUri, extensionResponses);
+
+ var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(resultUri);
+ if (parsedPositiveAssertion.claimedIdentifier && parsedPositiveAssertion.claimedIdentifier != discoveryResult.claimedIdentifier) {
+ discoveryResult.claimedIdentifier = parsedPositiveAssertion.claimedIdentifier;
+ trace('Authenticated as ' + parsedPositiveAssertion.claimedIdentifier);
+ }
+ } else {
+ respondingEndpoint.onAuthFailed();
+ }
+};
+
+window.dnoa_internal.isAuthSuccessful = function(resultUri) {
+ if (window.dnoa_internal.isOpenID2Response(resultUri)) {
+ return resultUri.getQueryArgValue("openid.mode") == "id_res";
+ } else {
+ return resultUri.getQueryArgValue("openid.mode") == "id_res" && !resultUri.containsQueryArg("openid.user_setup_url");
+ }
+};
+
+window.dnoa_internal.isOpenID2Response = function(resultUri) {
+ return resultUri.containsQueryArg("openid.ns");
+};
+
+/// <summary>Instantiates an object that stores discovery results of some identifier.</summary>
+window.dnoa_internal.DiscoveryResult = function(identifier, discoveryInfo) {
+ var thisDiscoveryResult = this;
+
+ /// <summary>
+ /// Instantiates an object that describes an OpenID service endpoint and facilitates
+ /// initiating and tracking an authentication request.
+ /// </summary>
+ function ServiceEndpoint(requestInfo, userSuppliedIdentifier) {
+ this.immediate = requestInfo.immediate ? new window.dnoa_internal.Uri(requestInfo.immediate) : null;
+ this.setup = requestInfo.setup ? new window.dnoa_internal.Uri(requestInfo.setup) : null;
+ this.endpoint = new window.dnoa_internal.Uri(requestInfo.endpoint);
+ this.host = this.endpoint.getHost();
+ this.userSuppliedIdentifier = userSuppliedIdentifier;
+ var thisServiceEndpoint = this; // closure so that delegates have the right instance
+ this.loginPopup = function(onAuthSuccess, onAuthFailed) {
+ thisServiceEndpoint.abort(); // ensure no concurrent attempts
+ window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, thisServiceEndpoint, { background: false });
+ thisDiscoveryResult.onAuthSuccess = onAuthSuccess;
+ thisDiscoveryResult.onAuthFailed = onAuthFailed;
+ var chromeHeight = 55; // estimated height of browser title bar and location bar
+ var bottomMargin = 45; // estimated bottom space on screen likely to include a task bar
+ var width = 1000;
+ var height = 600;
+ if (thisServiceEndpoint.setup.getQueryArgValue("openid.return_to").indexOf("dnoa.popupUISupported") >= 0) {
+ trace('This OP supports the UI extension. Using smaller window size.');
+ width = 500; // spec calls for 450px, but Yahoo needs 500px
+ height = 500;
+ } else {
+ trace("This OP doesn't appear to support the UI extension. Using larger window size.");
+ }
+
+ var left = (screen.width - width) / 2;
+ var top = (screen.height - bottomMargin - height - chromeHeight) / 2;
+ thisServiceEndpoint.popup = window.open(thisServiceEndpoint.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + left + ',top=' + top + ',width=' + width + ',height=' + height);
+
+ // If the OP supports the UI extension it MAY close its own window
+ // for a negative assertion. We must be able to recover from that scenario.
+ var thisServiceEndpointLocal = thisServiceEndpoint;
+ thisServiceEndpoint.popupCloseChecker = window.setInterval(function() {
+ if (thisServiceEndpointLocal.popup) {
+ try {
+ if (thisServiceEndpointLocal.popup.closed) {
+ // The window closed, either because the user closed it, canceled at the OP,
+ // or approved at the OP and the popup window closed itself due to our script.
+ // If we were graying out the entire page while the child window was up,
+ // we would probably revert that here.
+ window.clearInterval(thisServiceEndpointLocal.popupCloseChecker);
+ thisServiceEndpointLocal.popup = null;
+
+ // The popup may have managed to inform us of the result already,
+ // so check whether the callback method was cleared already, which
+ // would indicate we've already processed this.
+ if (window.dnoa_internal.processAuthorizationResult) {
+ trace('User or OP canceled by closing the window.');
+ window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: false });
+ if (thisDiscoveryResult.onAuthFailed) {
+ thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint);
+ }
+ }
+ }
+ } catch (e) {
+ // This usually happens because the popup is currently displaying the OP's
+ // page from another domain, which makes the popup temporarily off limits to us.
+ // Just skip this interval and wait for the next callback.
+ }
+ } else {
+ // if there's no popup, there's no reason to keep this timer up.
+ window.clearInterval(thisServiceEndpointLocal.popupCloseChecker);
+ }
+ }, 250);
+ };
+
+ this.loginBackgroundJob = function(iframe, timeout) {
+ thisServiceEndpoint.abort(); // ensure no concurrent attempts
+ if (timeout) {
+ thisServiceEndpoint.timeout = setTimeout(function() { thisServiceEndpoint.onAuthenticationTimedOut(); }, timeout);
+ }
+ window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, thisServiceEndpoint, { background: true });
+ trace('iframe hosting ' + thisServiceEndpoint.endpoint + ' now OPENING (timeout ' + timeout + ').');
+ //trace('initiating auth attempt with: ' + thisServiceEndpoint.immediate);
+ thisServiceEndpoint.iframe = iframe;
+ return {
+ url: thisServiceEndpoint.immediate.toString(),
+ onCanceled: function() {
+ thisServiceEndpoint.abort();
+ window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: true });
+ }
+ };
+ };
+
+ this.busy = function() {
+ return thisServiceEndpoint.iframe || thisServiceEndpoint.popup;
+ };
+
+ this.completeAttempt = function(successful) {
+ if (!thisServiceEndpoint.busy()) { return false; }
+ window.clearInterval(thisServiceEndpoint.timeout);
+ var background = thisServiceEndpoint.iframe !== null;
+ if (thisServiceEndpoint.iframe) {
+ trace('iframe hosting ' + thisServiceEndpoint.endpoint + ' now CLOSING.');
+ thisDiscoveryResult.frameManager.closeFrame(thisServiceEndpoint.iframe);
+ thisServiceEndpoint.iframe = null;
+ }
+ if (thisServiceEndpoint.popup) {
+ thisServiceEndpoint.popup.close();
+ thisServiceEndpoint.popup = null;
+ }
+ if (thisServiceEndpoint.timeout) {
+ window.clearTimeout(thisServiceEndpoint.timeout);
+ thisServiceEndpoint.timeout = null;
+ }
+
+ if (!successful && !thisDiscoveryResult.busy() && !thisDiscoveryResult.findSuccessfulRequest()) {
+ // fire the failed event with NO service endpoint indicating the entire auth attempt has failed.
+ window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, null, { background: background });
+ if (thisDiscoveryResult.onLastAttemptFailed) {
+ thisDiscoveryResult.onLastAttemptFailed(thisDiscoveryResult);
+ }
+ }
+
+ return true;
+ };
+
+ this.onAuthenticationTimedOut = function() {
+ var background = thisServiceEndpoint.iframe !== null;
+ if (thisServiceEndpoint.completeAttempt()) {
+ trace(thisServiceEndpoint.host + " timed out");
+ thisServiceEndpoint.result = window.dnoa_internal.timedOut;
+ }
+ window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: background });
+ };
+
+ this.onAuthSuccess = function(authUri, extensionResponses) {
+ var background = thisServiceEndpoint.iframe !== null;
+ if (thisServiceEndpoint.completeAttempt(true)) {
+ trace(thisServiceEndpoint.host + " authenticated!");
+ thisServiceEndpoint.result = window.dnoa_internal.authSuccess;
+ thisServiceEndpoint.successReceived = new Date();
+ thisServiceEndpoint.claimedIdentifier = authUri.getQueryArgValue('openid.claimed_id');
+ thisServiceEndpoint.response = authUri;
+ thisServiceEndpoint.extensionResponses = extensionResponses;
+ thisDiscoveryResult.abortAll();
+ if (thisDiscoveryResult.onAuthSuccess) {
+ thisDiscoveryResult.onAuthSuccess(thisDiscoveryResult, thisServiceEndpoint, extensionResponses);
+ }
+ window.dnoa_internal.fireAuthSuccess(thisDiscoveryResult, thisServiceEndpoint, extensionResponses, { background: background });
+ }
+ };
+
+ this.onAuthFailed = function() {
+ var background = thisServiceEndpoint.iframe !== null;
+ if (thisServiceEndpoint.completeAttempt()) {
+ trace(thisServiceEndpoint.host + " failed authentication");
+ thisServiceEndpoint.result = window.dnoa_internal.authRefused;
+ window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: background });
+ if (thisDiscoveryResult.onAuthFailed) {
+ thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint);
+ }
+ }
+ };
+
+ this.abort = function() {
+ if (thisServiceEndpoint.completeAttempt()) {
+ trace(thisServiceEndpoint.host + " aborted");
+ // leave the result as whatever it was before.
+ }
+ };
+
+ this.clear = function() {
+ thisServiceEndpoint.result = null;
+ thisServiceEndpoint.extensionResponses = null;
+ thisServiceEndpoint.successReceived = null;
+ thisServiceEndpoint.claimedIdentifier = null;
+ thisServiceEndpoint.response = null;
+ if (this.onCleared) {
+ this.onCleared(thisServiceEndpoint, thisDiscoveryResult);
+ }
+ if (thisDiscoveryResult.onCleared) {
+ thisDiscoveryResult.onCleared(thisDiscoveryResult, thisServiceEndpoint);
+ }
+ window.dnoa_internal.fireAuthCleared(thisDiscoveryResult, thisServiceEndpoint);
+ };
+
+ this.toString = function() {
+ return "[ServiceEndpoint: " + thisServiceEndpoint.host + "]";
+ };
+ }
+
+ this.cloneWithOneServiceEndpoint = function(serviceEndpoint) {
+ var clone = window.dnoa_internal.clone(this);
+ clone.userSuppliedIdentifier = serviceEndpoint.claimedIdentifier;
+
+ // Erase all SEPs except the given one, and put it into first position.
+ clone.length = 1;
+ for (var i = 0; i < this.length; i++) {
+ if (clone[i].endpoint.toString() == serviceEndpoint.endpoint.toString()) {
+ var tmp = clone[i];
+ clone[i] = null;
+ clone[0] = tmp;
+ } else {
+ clone[i] = null;
+ }
+ }
+
+ return clone;
+ };
+
+ this.userSuppliedIdentifier = identifier;
+ this.error = discoveryInfo.error;
+
+ if (discoveryInfo) {
+ this.claimedIdentifier = discoveryInfo.claimedIdentifier; // The claimed identifier may be null if the user provided an OP Identifier.
+ this.length = discoveryInfo.requests.length;
+ for (var i = 0; i < discoveryInfo.requests.length; i++) {
+ this[i] = new ServiceEndpoint(discoveryInfo.requests[i], identifier);
+ }
+ } else {
+ this.length = 0;
+ }
+
+ if (this.length === 0) {
+ trace('Discovery completed, but yielded no service endpoints.');
+ } else {
+ trace('Discovered claimed identifier: ' + (this.claimedIdentifier ? this.claimedIdentifier : "(directed identity)"));
+ }
+
+ // Add extra tracking bits and behaviors.
+ this.findByEndpoint = function(opEndpoint) {
+ for (var i = 0; i < thisDiscoveryResult.length; i++) {
+ if (thisDiscoveryResult[i].endpoint == opEndpoint) {
+ return thisDiscoveryResult[i];
+ }
+ }
+ };
+
+ this.busy = function() {
+ for (var i = 0; i < thisDiscoveryResult.length; i++) {
+ if (thisDiscoveryResult[i].busy()) {
+ return true;
+ }
+ }
+ };
+
+ // Add extra tracking bits and behaviors.
+ this.findSuccessfulRequest = function() {
+ for (var i = 0; i < thisDiscoveryResult.length; i++) {
+ if (thisDiscoveryResult[i].result === window.dnoa_internal.authSuccess) {
+ return thisDiscoveryResult[i];
+ }
+ }
+ };
+
+ this.abortAll = function() {
+ if (thisDiscoveryResult.frameManager) {
+ // Abort all other asynchronous authentication attempts that may be in progress
+ // for this particular claimed identifier.
+ thisDiscoveryResult.frameManager.cancelAllWork();
+ for (var i = 0; i < thisDiscoveryResult.length; i++) {
+ thisDiscoveryResult[i].abort();
+ }
+ } else {
+ trace('abortAll called without a frameManager being previously set.');
+ }
+ };
+
+ /// <summary>Initiates an asynchronous checkid_immediate login attempt against all possible service endpoints for an Identifier.</summary>
+ /// <param name="frameManager">The work queue for authentication iframes.</param>
+ /// <param name="onAuthSuccess">Fired when an endpoint responds affirmatively.</param>
+ /// <param name="onAuthFailed">Fired when an endpoint responds negatively.</param>
+ /// <param name="onLastAuthFailed">Fired when all authentication attempts have responded negatively or timed out.</param>
+ /// <param name="timeout">Timeout for an individual service endpoint to respond before the iframe closes.</param>
+ this.loginBackground = function(frameManager, onAuthSuccess, onAuthFailed, onLastAuthFailed, timeout) {
+ if (!frameManager) {
+ throw "No frameManager specified.";
+ }
+ var priorSuccessRespondingEndpoint = thisDiscoveryResult.findSuccessfulRequest();
+ if (priorSuccessRespondingEndpoint) {
+ // In this particular case, we do not fire an AuthStarted event.
+ window.dnoa_internal.fireAuthSuccess(thisDiscoveryResult, priorSuccessRespondingEndpoint, priorSuccessRespondingEndpoint.extensionResponses, { background: true });
+ if (onAuthSuccess) {
+ onAuthSuccess(thisDiscoveryResult, priorSuccessRespondingEndpoint);
+ }
+ } else {
+ if (thisDiscoveryResult.busy()) {
+ trace('Warning: DiscoveryResult.loginBackground invoked while a login attempt is already in progress. Discarding second login request.', 'red');
+ return;
+ }
+ thisDiscoveryResult.frameManager = frameManager;
+ thisDiscoveryResult.onAuthSuccess = onAuthSuccess;
+ thisDiscoveryResult.onAuthFailed = onAuthFailed;
+ thisDiscoveryResult.onLastAttemptFailed = onLastAuthFailed;
+ // Notify listeners that general authentication is beginning. Individual ServiceEndpoints
+ // will fire their own events as each of them begin their iframe 'job'.
+ window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, null, { background: true });
+ if (thisDiscoveryResult.length > 0) {
+ for (var i = 0; i < thisDiscoveryResult.length; i++) {
+ thisDiscoveryResult.frameManager.enqueueWork(thisDiscoveryResult[i].loginBackgroundJob, timeout);
+ }
+ }
+ }
+ };
+
+ this.toString = function() {
+ return "[DiscoveryResult: " + thisDiscoveryResult.userSuppliedIdentifier + "]";
+ };
+};
+
+/// <summary>
+/// Called in a page had an AJAX control that had already obtained a positive assertion
+/// when a postback occurred, and now that control wants to restore its 'authenticated' state.
+/// </summary>
+/// <param name="positiveAssertion">The string form of the URI that contains the positive assertion.</param>
+window.dnoa_internal.deserializePreviousAuthentication = function(positiveAssertion) {
+ if (!positiveAssertion || positiveAssertion.length === 0) {
+ return;
+ }
+
+ trace('Revitalizing an old positive assertion from a prior postback.');
+
+ // The control ensures that we ALWAYS have an OpenID 2.0-style claimed_id attribute, even against
+ // 1.0 Providers via the return_to URL mechanism.
+ var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(positiveAssertion);
+
+ // We weren't given a full discovery history, but we can spoof this much from the
+ // authentication assertion.
+ trace('Deserialized claimed_id: ' + parsedPositiveAssertion.claimedIdentifier + ' and endpoint: ' + parsedPositiveAssertion.endpoint);
+ var discoveryInfo = {
+ claimedIdentifier: parsedPositiveAssertion.claimedIdentifier,
+ requests: [{ endpoint: parsedPositiveAssertion.endpoint}]
+ };
+
+ discoveryResult = new window.dnoa_internal.DiscoveryResult(parsedPositiveAssertion.userSuppliedIdentifier, discoveryInfo);
+ window.dnoa_internal.discoveryResults[parsedPositiveAssertion.userSuppliedIdentifier] = discoveryResult;
+ discoveryResult[0].result = window.dnoa_internal.authSuccess;
+ discoveryResult.successAuthData = positiveAssertion;
+
+ // restore old state from before postback
+ window.dnoa_internal.fireAuthSuccess(discoveryResult, discoveryResult[0], null, { background: true, deserialized: true });
+};
+
+window.dnoa_internal.PositiveAssertion = function(uri) {
+ uri = new window.dnoa_internal.Uri(uri.toString());
+ this.endpoint = new window.dnoa_internal.Uri(uri.getQueryArgValue("dnoa.op_endpoint"));
+ this.userSuppliedIdentifier = uri.getQueryArgValue('dnoa.userSuppliedIdentifier');
+ this.claimedIdentifier = uri.getQueryArgValue('openid.claimed_id');
+ if (!this.claimedIdentifier) {
+ this.claimedIdentifier = uri.getQueryArgValue('dnoa.claimed_id');
+ }
+ this.toString = function() { return uri.toString(); };
+};
+
+window.dnoa_internal.clone = function(obj) {
+ if (obj === null || typeof (obj) != 'object' || !isNaN(obj)) { // !isNaN catches Date objects
+ return obj;
+ }
+
+ var temp = {};
+ for (var key in obj) {
+ temp[key] = window.dnoa_internal.clone(obj[key]);
+ }
+
+ // Copy over some built-in methods that were not included in the above loop,
+ // but nevertheless may have been overridden.
+ temp.toString = window.dnoa_internal.clone(obj.toString);
+
+ return temp;
+};
+
+// Deserialized the preloaded discovery results
+window.dnoa_internal.loadPreloadedDiscoveryResults = function(preloadedDiscoveryResults) {
+ trace('found ' + preloadedDiscoveryResults.length + ' preloaded discovery results.');
+ for (var i = 0; i < preloadedDiscoveryResults.length; i++) {
+ var result = preloadedDiscoveryResults[i];
+ if (!window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier]) {
+ window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier] = new window.dnoa_internal.DiscoveryResult(result.userSuppliedIdentifier, result.discoveryResult);
+ trace('Preloaded discovery on: ' + window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier].userSuppliedIdentifier);
+ } else {
+ trace('Skipped preloaded discovery on: ' + window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier].userSuppliedIdentifier + ' because we have a cached discovery result on it.');
+ }
+ }
+};
+
+window.dnoa_internal.clearExpiredPositiveAssertions = function() {
+ for (identifier in window.dnoa_internal.discoveryResults) {
+ var discoveryResult = window.dnoa_internal.discoveryResults[identifier];
+ if (typeof (discoveryResult) != 'object') { continue; } // skip functions
+ for (var i = 0; i < discoveryResult.length; i++) {
+ if (discoveryResult[i] && discoveryResult[i].result === window.dnoa_internal.authSuccess) {
+ if (new Date() - discoveryResult[i].successReceived > window.dnoa_internal.maxPositiveAssertionLifetime) {
+ // This positive assertion is too old, and may eventually be rejected by DNOA during verification.
+ // Let's clear out the positive assertion so it can be renewed.
+ trace('Clearing out expired positive assertion from ' + discoveryResult[i].host);
+ discoveryResult[i].clear();
+ }
+ }
+ }
+ }
+};
+
+window.setInterval(window.dnoa_internal.clearExpiredPositiveAssertions, 1000);