//----------------------------------------------------------------------- // 0) { return existing[0]; } var hiddenField = document.createElement('input'); hiddenField.setAttribute("name", name); hiddenField.setAttribute("type", "hidden"); box.parentForm.appendChild(hiddenField); return hiddenField; } box.dnoi_internal.retryButton = box.dnoi_internal.constructButton(retryButtonText, retryButtonToolTip, function() { box.timeout += 5000; // give the retry attempt 5s longer than the last attempt box.dnoi_internal.performDiscovery(); return false; }); box.dnoi_internal.openid_logo = box.dnoi_internal.constructIcon(openid_logo_url, null, false, true); box.dnoi_internal.op_logo = box.dnoi_internal.constructIcon('', authenticatedByToolTip, false, false, "16px"); box.dnoi_internal.op_logo.style.maxWidth = '16px'; box.dnoi_internal.spinner = box.dnoi_internal.constructIcon(spinner_url, busyToolTip, true); box.dnoi_internal.success_icon = box.dnoi_internal.constructIcon(success_icon_url, authenticatedAsToolTip, true); box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true); box.dnoi_internal.dnoi_logo = box.dnoi_internal.openid_logo; box.dnoi_internal.setVisualCue = function(state, authenticatedBy, authenticatedAs, providers, errorMessage) { box.dnoi_internal.openid_logo.style.visibility = 'hidden'; box.dnoi_internal.dnoi_logo.style.visibility = 'hidden'; box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.openid_logo.originalTitle; box.dnoi_internal.spinner.style.visibility = 'hidden'; box.dnoi_internal.success_icon.style.visibility = 'hidden'; box.dnoi_internal.failure_icon.style.visibility = 'hidden'; box.dnoi_internal.retryButton.style.visibility = 'hidden'; if (box.dnoi_internal.loginButton) { box.dnoi_internal.loginButton.destroy(); box.dnoi_internal.loginButton = null; } if (box.dnoi_internal.postbackLoginButton) { box.dnoi_internal.postbackLoginButton.destroy(); box.dnoi_internal.postbackLoginButton = null; } box.title = ''; box.dnoi_internal.state = state; var opLogo; if (state == "discovering") { box.dnoi_internal.dnoi_logo.style.visibility = 'visible'; box.dnoi_internal.spinner.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; box.title = ''; window.status = "Discovering OpenID Identifier '" + box.value + "'..."; } else if (state == "authenticated") { opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; box.dnoi_internal.op_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } //trace("OP icon size: " + box.dnoi_internal.op_logo.fileSize); // The filesize check just doesn't seem to work any more. if (!opLogo) {// || box.dnoi_internal.op_logo.fileSize == -1 /*IE*/ || box.dnoi_internal.op_logo.fileSize === undefined /* FF */) { trace('recovering from missing OP icon'); box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } if (showLoginPostBackButton) { box.dnoi_internal.postbackLoginButton = box.dnoi_internal.createLoginPostBackButton(); } else { box.dnoi_internal.success_icon.style.visibility = 'visible'; box.dnoi_internal.success_icon.title = box.dnoi_internal.success_icon.originalTitle.replace('{0}', authenticatedAs); } box.title = box.dnoi_internal.claimedIdentifier; window.status = "Authenticated as " + authenticatedAs; } else if (state == "setup") { opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; } else { box.dnoi_internal.openid_logo.style.visibility = 'visible'; } box.dnoi_internal.loginButton = box.dnoi_internal.createLoginButton(providers); box.dnoi_internal.claimedIdentifier = null; window.status = "Authentication requires user interaction."; } else if (state == "failed") { box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.retryButton.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; window.status = authenticationFailedToolTip; box.title = authenticationFailedToolTip; } else if (state == "failednoretry") { box.dnoi_internal.failure_icon.title = errorMessage; box.dnoi_internal.failure_icon.style.visibility = 'visible'; box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; window.status = errorMessage; box.title = errorMessage; } else if (state == '' || !state) { box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.title = ''; box.dnoi_internal.claimedIdentifier = null; window.status = null; } else { box.dnoi_internal.claimedIdentifier = null; trace('unrecognized state ' + state); } if (box.onStateChanged) { box.onStateChanged(state); } }; box.dnoi_internal.isBusy = function() { var lastDiscovery = window.dnoa_internal.discoveryResults[box.lastDiscoveredIdentifier]; return box.dnoi_internal.state == 'discovering' || (lastDiscovery && lastDiscovery.busy()); }; box.dnoi_internal.canAttemptLogin = function() { if (box.value.length === 0) { return false; } if (!window.dnoa_internal.discoveryResults[box.value]) { return false; } if (box.dnoi_internal.state == 'failed') { return false; } return true; }; box.dnoi_internal.getUserSuppliedIdentifierResults = function() { return window.dnoa_internal.discoveryResults[box.value]; }; box.dnoi_internal.isAuthenticated = function() { var results = box.dnoi_internal.getUserSuppliedIdentifierResults(); return results && results.findSuccessfulRequest(); }; box.dnoi_internal.onSubmit = function() { var hiddenField = findOrCreateHiddenField(); if (box.dnoi_internal.isAuthenticated()) { // stick the result in a hidden field so the RP can verify it hiddenField.setAttribute("value", window.dnoa_internal.discoveryResults[box.value].successAuthData); } else { hiddenField.setAttribute("value", ''); if (box.dnoi_internal.isBusy()) { alert(loginInProgressMessage); } else { if (box.value.length > 0) { // submitPending will be true if we've already tried deferring submit for a login, // in which case we just want to display a box to the user. if (box.dnoi_internal.submitPending || !box.dnoi_internal.canAttemptLogin()) { alert(identifierRequiredMessage); } else { // The user hasn't clicked "Login" yet. We'll click login for him, // after leaving a note for ourselves to automatically click submit // when login is complete. box.dnoi_internal.submitPending = box.dnoi_internal.submitButtonJustClicked; if (box.dnoi_internal.submitPending === null) { box.dnoi_internal.submitPending = true; } box.dnoi_internal.loginButton.onclick(); return false; // abort submit for now } } else { return true; } } return false; } return true; }; /// /// Records which submit button caused this openid box to question whether it /// was ready to submit the user's identifier so that that button can be re-invoked /// automatically after authentication completes. /// box.dnoi_internal.setLastSubmitButtonClicked = function(evt) { var button; if (evt.target) { button = evt.target; } else { button = evt.srcElement; } box.dnoi_internal.submitButtonJustClicked = button; }; // Find all submit buttons and hook their click events so that we can validate // whether we are ready for the user to postback. var inputs = document.getElementsByTagName('input'); for (var i = 0; i < inputs.length; i++) { var el = inputs[i]; if (el.type == 'submit') { if (el.attachEvent) { el.attachEvent("onclick", box.dnoi_internal.setLastSubmitButtonClicked); } else { el.addEventListener("click", box.dnoi_internal.setLastSubmitButtonClicked, true); } } } /// /// Returns the URL of the authenticating OP's logo so it can be displayed to the user. /// /// The OP Endpoint, if known. box.dnoi_internal.deriveOPFavIcon = function(opUri) { if (!opUri) { var idresults = box.dnoi_internal.getUserSuppliedIdentifierResults(); var response = idresults ? idresults.successAuthData : null; if (!response || response.length === 0) { trace('No favicon because no successAuthData.'); return; } var authResult = new window.dnoa_internal.Uri(response); if (authResult.getQueryArgValue("openid.op_endpoint")) { opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("openid.op_endpoint")); } else if (authResult.getQueryArgValue("dnoa.op_endpoint")) { opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("dnoa.op_endpoint")); } else if (authResult.getQueryArgValue("openid.user_setup_url")) { opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("openid.user_setup_url")); } else { return null; } } var favicon = opUri.getAuthority() + "/favicon.ico"; trace('Guessing favicon location of: ' + favicon); return favicon; }; /***************************************** * Event Handlers *****************************************/ window.dnoa_internal.addDiscoveryStarted(function(identifier) { if (identifier == box.value) { box.dnoi_internal.setVisualCue('discovering'); } }, box); window.dnoa_internal.addDiscoverySuccess(function(identifier, discoveryResult, state) { if (identifier == box.value && (box.dnoi_internal.state == 'discovering' || !box.dnoi_internal.state)) { // Start pre-fetching the OP favicons for (var i = 0; i < discoveryResult.length; i++) { var favicon = box.dnoi_internal.deriveOPFavIcon(discoveryResult[i].endpoint); if (favicon) { trace('Prefetching ' + favicon); box.dnoi_internal.prefetchImage(favicon); } } if (discoveryResult.length > 0) { discoveryResult.loginBackground( box.dnoi_internal.authenticationIFrames, null, null, null, box.timeout); } else { // discovery completed successfully -- it just didn't yield any service endpoints. box.dnoi_internal.setVisualCue('failednoretry', null, null, null, discoveryResult.error); if (discoveryResult.error) { box.title = discoveryResult.error; } } } }, box); window.dnoa_internal.addDiscoveryFailed(function(identifier, message) { if (identifier == box.value) { box.dnoi_internal.setVisualCue('failed'); if (message) { box.title = message; } } }, box); window.dnoa_internal.addAuthStarted(function(discoveryResult, serviceEndpoint, state) { if (discoveryResult.userSuppliedIdentifier == box.value) { box.dnoi_internal.setVisualCue('discovering'); } }, box); window.dnoa_internal.addAuthSuccess(function(discoveryResult, serviceEndpoint, extensionResponses, state) { if (discoveryResult.userSuppliedIdentifier == box.value) { // visual cue that auth was successful var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(discoveryResult.successAuthData); box.dnoi_internal.claimedIdentifier = parsedPositiveAssertion.claimedIdentifier; // If the OP doesn't support delegation, "correct" the identifier the user entered // so he realizes his identity didn't stick. But don't change out OP Identifiers. if (discoveryResult.claimedIdentifier && discoveryResult.claimedIdentifier != parsedPositiveAssertion.claimedIdentifier) { box.value = parsedPositiveAssertion.claimedIdentifier; box.lastDiscoveredIdentifier = box.value; // Also inject a fake discovery result for this new identifier to keep the UI from performing // discovery on the new identifier (the RP will perform the necessary verification server-side). if (!window.dnoa_internal.discoveryResults[box.value]) { // We must make sure that the only service endpoint from the earlier discovery that // is copied over is the one that sent the assertion just now. Deep clone, then strip // out the other SEPs. window.dnoa_internal.discoveryResults[box.value] = discoveryResult.cloneWithOneServiceEndpoint(serviceEndpoint); } } box.dnoi_internal.setVisualCue('authenticated', parsedPositiveAssertion.endpoint, parsedPositiveAssertion.claimedIdentifier); if (box.dnoi_internal.onauthenticated) { box.dnoi_internal.onauthenticated(box, extensionResponses); } if (showLoginPostBackButton && !state.background) { box.dnoi_internal.postback(discoveryResult, serviceEndpoint, extensionResponses, state); } else if (box.dnoi_internal.submitPending) { // We submit the form BEFORE resetting the submitPending so // the submit handler knows we've already tried this route. if (box.dnoi_internal.submitPending === true) { box.parentForm.submit(); } else { box.dnoi_internal.submitPending.click(); } box.dnoi_internal.submitPending = null; } else if (!state.deserialized && autoPostback) { // as long as this is a fresh auth response, postback to the server if configured to do so. box.dnoi_internal.postback(discoveryResult, serviceEndpoint, extensionResponses, state); } } }, box); window.dnoa_internal.addAuthFailed(function(discoveryResult, serviceEndpoint, state) { if (discoveryResult.userSuppliedIdentifier == box.value) { box.dnoi_internal.submitPending = null; if (!serviceEndpoint || !state.background) { // if the last service endpoint just turned the user down box.dnoi_internal.displayLoginButton(discoveryResult); } } }, box); window.dnoa_internal.addAuthCleared(function(discoveryResult, serviceEndpoint) { if (discoveryResult.userSuppliedIdentifier == box.value) { if (!discoveryResult.findSuccessfulRequest()) { // attempt to renew the positive assertion. discoveryResult.loginBackground( box.dnoi_internal.authenticationIFrames, null, null, null, box.timeout); } } }, box); /***************************************** * Flow *****************************************/ box.dnoi_internal.displayLoginButton = function(discoveryResult) { trace('No asynchronous authentication attempt is in progress. Display setup view.'); var providers = []; for (var i = 0; i < discoveryResult.length; i++) { var favicon = box.dnoi_internal.deriveOPFavIcon(discoveryResult[i].endpoint); var img = ''; providers.push({ text: img + discoveryResult[i].host, value: discoveryResult[i] }); } // visual cue that auth failed box.dnoi_internal.setVisualCue('setup', null, null, providers); }; /// Called to initiate discovery on some identifier. box.dnoi_internal.performDiscovery = function() { box.dnoi_internal.authenticationIFrames.closeFrames(); box.lastDiscoveredIdentifier = box.value; var openid = new window.OpenIdIdentifier(box.value); openid.discover(); }; box.onblur = function(event) { if (box.lastDiscoveredIdentifier != box.value || !box.dnoi_internal.state) { if (box.value.length > 0) { box.dnoi_internal.resetAndDiscover(); } else { box.dnoi_internal.setVisualCue(); } } return true; }; //{ var rate = NaN; var lastValue = box.value; var keyPresses = 0; var startTime = null; var lastKeyPress = null; var discoveryTimer; function cancelTimer() { if (discoveryTimer) { trace('canceling timer', 'gray'); clearTimeout(discoveryTimer); discoveryTimer = null; } } function identifierSanityCheck(id) { return id.match("^[=@+$!(].+|.*?\\..*[^\\.]|\\w+://.+"); } function discover() { cancelTimer(); trace('typist discovery candidate', 'gray'); if (identifierSanityCheck(box.value)) { trace('typist discovery begun', 'gray'); box.dnoi_internal.performDiscovery(); } else { trace('typist discovery canceled due to incomplete identifier.', 'gray'); } } function reset() { keyPresses = 0; startTime = null; rate = NaN; trace('resetting state', 'gray'); } box.dnoi_internal.resetAndDiscover = function() { reset(); discover(); }; box.onkeyup = function(e) { e = e || window.event; // for IE if (new Date() - lastKeyPress > 3000) { // the user seems to have altogether stopped typing, // so reset our typist speed detector. reset(); } lastKeyPress = new Date(); var newValue = box.value; if (e.keyCode == 13) { if (box.dnoi_internal.state === 'setup') { box.dnoi_internal.loginButton.click(); } else if (box.dnoi_internal.postbackLoginButton) { box.dnoi_internal.postbackLoginButton.click(); } else { discover(); } } else { if (lastValue != newValue && newValue != box.lastDiscoveredIdentifier) { box.dnoi_internal.setVisualCue(); if (newValue.length === 0) { reset(); } else if (Math.abs((lastValue || '').length - newValue.length) > 1) { // One key press is responsible for multiple character changes. // The user may have pasted in his identifier in which case // we want to begin discovery immediately. trace(newValue + ': paste detected (old value ' + lastValue + ')', 'gray'); discover(); } else { keyPresses++; var timeout = 3000; // timeout to use if we don't have enough keying to figure out type rate if (startTime === null) { startTime = new Date(); } else if (keyPresses > 1) { cancelTimer(); rate = (new Date() - startTime) / keyPresses; var minTimeout = 300; var maxTimeout = 3000; var typistFactor = 5; timeout = Math.max(minTimeout, Math.min(rate * typistFactor, maxTimeout)); } trace(newValue + ': setting timer for ' + timeout, 'gray'); discoveryTimer = setTimeout(discover, timeout); } } } trace(newValue + ': updating lastValue', 'gray'); lastValue = newValue; return true; }; //} box.getClaimedIdentifier = function() { return box.dnoi_internal.claimedIdentifier; }; // If an identifier is preset on the box, perform discovery on it, but only // if there isn't a prior authentication that we're about to deserialize. if (box.value.length > 0 && findOrCreateHiddenField().value.length === 0) { trace('jumpstarting discovery on ' + box.value + ' because it was preset.'); box.dnoi_internal.performDiscovery(); } // Restore a previously achieved state (from pre-postback) if it is given. window.dnoa_internal.deserializePreviousAuthentication(findOrCreateHiddenField().value); // public methods box.setValue = function(value) { box.value = value; if (box.value) { box.dnoi_internal.performDiscovery(); } }; // public events // box.onStateChanged(state) }