//-----------------------------------------------------------------------
//
// 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
//
//-----------------------------------------------------------------------
if (window.dnoa_internal === undefined) {
window.dnoa_internal = {};
}
/// Removes a given element from the array.
/// True if the element was in the array, or false if it was not found.
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) {
/// Wraps a given function with a check so that the function only executes when a given element is still in the DOM.
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';
/// Instantiates a new FrameManager.
/// The maximum number of concurrent 'jobs' (authentication attempts).
window.dnoa_internal.FrameManager = function(maxFrames) {
this.queuedWork = [];
this.frames = [];
this.maxFrames = maxFrames;
/// Called to queue up some work that will use an iframe as soon as it is available.
///
/// 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.
///
/// Arbitrary additional parameter to pass to the job.
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 });
}
};
/// Clears the job queue and immediately closes all iframes.
this.cancelAllWork = function() {
trace('Canceling all open and pending iframes.');
while (this.queuedWork.pop()) { }
this.closeFrames();
};
/// An event fired when a frame is closing.
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;
};
};
/// Instantiates an object that represents an OpenID Identifier.
window.OpenIdIdentifier = function(identifier) {
if (!identifier || identifier.length === 0) {
throw 'Error: trying to create OpenIdIdentifier for null or empty string.';
}
/// Performs discovery on the identifier.
/// A function(DiscoveryResult) callback to be called when discovery has completed successfully.
/// A function callback to be called when discovery has completed in failure.
this.discover = function(onDiscoverSuccess, onDiscoverFailure) {
/// Receives the results of a successful discovery (even if it yielded 0 results).
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);
}
}
}
}
/// Receives the discovery failure notification.
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);
}
};
/// Performs discovery and immediately begins checkid_setup to authenticate the user using a given identifier.
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();
}
}
}
});
};
/// Performs discovery and immediately begins checkid_immediate on all discovered endpoints.
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;
};
};
/// Invoked by RP web server when an authentication has completed.
/// The duty of this method is to distribute the notification to the appropriate tracking object.
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");
};
/// Instantiates an object that stores discovery results of some identifier.
window.dnoa_internal.DiscoveryResult = function(identifier, discoveryInfo) {
var thisDiscoveryResult = this;
///
/// Instantiates an object that describes an OpenID service endpoint and facilitates
/// initiating and tracking an authentication request.
///
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.');
}
};
/// Initiates an asynchronous checkid_immediate login attempt against all possible service endpoints for an Identifier.
/// The work queue for authentication iframes.
/// Fired when an endpoint responds affirmatively.
/// Fired when an endpoint responds negatively.
/// Fired when all authentication attempts have responded negatively or timed out.
/// Timeout for an individual service endpoint to respond before the iframe closes.
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 + "]";
};
};
///
/// 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.
///
/// The string form of the URI that contains the positive assertion.
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);