// Copyright 2009 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // PopupManager is a library to facilitate integration with OpenID // identity providers (OP)s that support a pop-up authentication interface. // To create a popup window, you first construct a popupOpener customized // for your site and a particular identity provider, E.g.: // // var googleOpener = popupManager.createOpener(openidParams); // // where 'openidParams' are customized for Google in this instance. // (typically you just change the openidpoint, the version number // (the openid.ns parameter) and the extensions based on what // the OP supports. // OpenID libraries can often discover these properties // automatically from the location of an XRD document. // // Then, you can either directly call // googleOpener.popup(width, height), where 'width' and 'height' are your choices // for popup size, or you can display a button 'Sign in with Google' and set the //..'onclick' handler of the button to googleOpener.popup() var popupManager = {}; // Library constants popupManager.constants = { 'darkCover' : 'popupManager_darkCover_div', 'darkCoverStyle' : ['position:absolute;', 'top:0px;', 'left:0px;', 'padding-right:0px;', 'padding-bottom:0px;', 'background-color:#000000;', 'opacity:0.5;', //standard-compliant browsers '-moz-opacity:0.5;', // old Mozilla 'filter:alpha(opacity=0.5);', // IE 'z-index:10000;', 'width:100%;', 'height:100%;' ].join(''), 'openidSpec' : { 'identifier_select' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select', 'namespace2' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0' } }; // Computes the size of the window contents. Returns a pair of // coordinates [width, height] which can be [0, 0] if it was not possible // to compute the values. popupManager.getWindowInnerSize = function() { var width = 0; var height = 0; var elem = null; if ('innerWidth' in window) { // For non-IE width = window.innerWidth; height = window.innerHeight; } else { // For IE, if (('BackCompat' === window.document.compatMode) && ('body' in window.document)) { elem = window.document.body; } else if ('documentElement' in window.document) { elem = window.document.documentElement; } if (elem !== null) { width = elem.offsetWidth; height = elem.offsetHeight; } } return [width, height]; }; // Computes the coordinates of the parent window. // Gets the coordinates of the parent frame popupManager.getParentCoords = function() { var width = 0; var height = 0; if ('screenLeft' in window) { // IE-compatible variants width = window.screenLeft; height = window.screenTop; } else if ('screenX' in window) { // Firefox-compatible width = window.screenX; height = window.screenY; } return [width, height]; }; // Computes the coordinates of the new window, so as to center it // over the parent frame popupManager.getCenteredCoords = function(width, height) { var parentSize = this.getWindowInnerSize(); var parentPos = this.getParentCoords(); var xPos = parentPos[0] + Math.max(0, Math.floor((parentSize[0] - width) / 2)); var yPos = parentPos[1] + Math.max(0, Math.floor((parentSize[1] - height) / 2)); return [xPos, yPos]; }; // A utility class, implements an onOpenHandler that darkens the screen // by overlaying it with a semi-transparent black layer. To use, ensure that // no screen element has a z-index at or above 10000. // This layer will be suppressed automatically after the screen closes. // // Note: If you want to perform other operations before opening the popup, but // also would like the screen to darken, you can define a custom handler // as such: // var myOnOpenHandler = function(inputs) { // .. do something // popupManager.darkenScreen(); // .. something else // }; // Then you pass myOnOpenHandler as input to the opener, as in: // var openidParams = {}; // openidParams.onOpenHandler = myOnOpenHandler; // ... other customizations // var myOpener = popupManager.createOpener(openidParams); popupManager.darkenScreen = function() { var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); if (!darkCover) { darkCover = window.document.createElement('div'); darkCover['id'] = window.popupManager.constants['darkCover']; darkCover.setAttribute('style', window.popupManager.constants['darkCoverStyle']); window.document.body.appendChild(darkCover); } darkCover.style.visibility = 'visible'; }; // Returns a an object that can open a popup window customized for an OP & RP. // to use you call var opener = popupManager.cretePopupOpener(openidParams); // and then you can assign the 'onclick' handler of a button to // opener.popup(width, height), where width and height are the values of the popup size; // // To use it, you would typically have code such as: // var myLoginCheckFunction = ... some AJAXy call or page refresh operation // that will cause the user to see the logged-in experience in the current page. // var openidParams = { realm : 'openid.realm', returnToUrl : 'openid.return_to', // opEndpoint : 'openid.op_endpoint', onCloseHandler : myLoginCheckFunction, // shouldEncodeUrls : 'true' (default) or 'false', extensions : myOpenIDExtensions }; // // Here extensions include any OpenID extensions that you support. For instance, // if you support Attribute Exchange v.1.0, you can say: // (Example for attribute exchange request for email and name, // assuming that shouldEncodeUrls = 'true':) // var myOpenIDExtensions = { // 'openid.ax.ns' : 'http://openid.net/srv/ax/1.0', // 'openid.ax.type.email' : 'http://axschema.org/contact/email', // 'openid.ax.type.name1' : 'http://axschema.org/namePerson/first', // 'openid.ax.type.name2' : 'http://axschema.org/namePerson/last', // 'openid.ax.required' : 'email,name1,name2' }; // Note that the 'ui' namespace is reserved by this library for the OpenID // UI extension, and that the mode 'popup' is automatically applied. // If you wish to make use of the 'language' feature of the OpenID UI extension // simply add the following entry (example assumes the language requested // is Swiss French: // var my OpenIDExtensions = { // ... // other extension parameters // 'openid.ui.language' : 'fr_CH', // ... }; popupManager.createPopupOpener = (function(openidParams) { var interval_ = null; var popupWindow_ = null; var that = this; var shouldEscape_ = ('shouldEncodeUrls' in openidParams) ? openidParams.shouldEncodeUrls : true; var encodeIfRequested_ = function(url) { return (shouldEscape_ ? encodeURIComponent(url) : url); }; var identifier_ = ('identifier' in openidParams) ? encodeIfRequested_(openidParams.identifier) : this.constants.openidSpec.identifier_select; var identity_ = ('identity' in openidParams) ? encodeIfRequested_(openidParams.identity) : this.constants.openidSpec.identifier_select; var openidNs_ = ('namespace' in openidParams) ? encodeIfRequested_(openidParams.namespace) : this.constants.openidSpec.namespace2; var onOpenHandler_ = (('onOpenHandler' in openidParams) && ('function' === typeof(openidParams.onOpenHandler))) ? openidParams.onOpenHandler : this.darkenScreen; var onCloseHandler_ = (('onCloseHandler' in openidParams) && ('function' === typeof(openidParams.onCloseHandler))) ? openidParams.onCloseHandler : null; var returnToUrl_ = ('returnToUrl' in openidParams) ? openidParams.returnToUrl : null; var realm_ = ('realm' in openidParams) ? openidParams.realm : null; var endpoint_ = ('opEndpoint' in openidParams) ? openidParams.opEndpoint : null; var extensions_ = ('extensions' in openidParams) ? openidParams.extensions : null; // processes key value pairs, escaping any input; var keyValueConcat_ = function(keyValuePairs) { var result = ""; for (key in keyValuePairs) { result += ['&', key, '=', encodeIfRequested_(keyValuePairs[key])].join(''); } return result; }; //Assembles the OpenID request from customizable parameters var buildUrlToOpen_ = function() { var connector = '&'; var encodedUrl = null; var urlToOpen = null; if ((null === endpoint_) || (null === returnToUrl_)) { return; } if (endpoint_.indexOf('?') === -1) { connector = '?'; } encodedUrl = encodeIfRequested_(returnToUrl_); urlToOpen = [ endpoint_, connector, 'openid.ns=', openidNs_, '&openid.mode=checkid_setup', '&openid.claimed_id=', identifier_, '&openid.identity=', identity_, '&openid.return_to=', encodedUrl ].join(''); if (realm_ !== null) { urlToOpen += "&openid.realm=" + encodeIfRequested_(realm_); } if (extensions_ !== null) { urlToOpen += keyValueConcat_(extensions_); } urlToOpen += '&openid.ns.ui=' + encodeURIComponent( 'http://specs.openid.net/extensions/ui/1.0'); urlToOpen += '&openid.ui.mode=popup'; return urlToOpen; }; // Tests that the popup window has closed var isPopupClosed_ = function() { return (!popupWindow_ || popupWindow_.closed); }; // Check to perform at each execution of the timed loop. It also triggers // the action that follows the closing of the popup var waitForPopupClose_ = function() { if (isPopupClosed_()) { popupWindow_ = null; var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); if (darkCover) { darkCover.style.visibility = 'hidden'; } if (onCloseHandler_ !== null) { onCloseHandler_(); } if ((null !== interval_)) { window.clearInterval(interval_); interval_ = null; } } }; return { // Function that opens the window. popup: function(width, height) { var urlToOpen = buildUrlToOpen_(); if (onOpenHandler_ !== null) { onOpenHandler_(); } var coordinates = that.getCenteredCoords(width, height); popupWindow_ = window.open(urlToOpen, "", "width=" + width + ",height=" + height + ",status=1,location=1,resizable=yes" + ",left=" + coordinates[0] +",top=" + coordinates[1]); interval_ = window.setInterval(waitForPopupClose_, 80); return true; } }; });