1// Copyright 2009 Google Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15 16// PopupManager is a library to facilitate integration with OpenID 17// identity providers (OP)s that support a pop-up authentication interface. 18// To create a popup window, you first construct a popupOpener customized 19// for your site and a particular identity provider, E.g.: 20// 21// var googleOpener = popupManager.createOpener(openidParams); 22// 23// where 'openidParams' are customized for Google in this instance. 24// (typically you just change the openidpoint, the version number 25// (the openid.ns parameter) and the extensions based on what 26// the OP supports. 27// OpenID libraries can often discover these properties 28// automatically from the location of an XRD document. 29// 30// Then, you can either directly call 31// googleOpener.popup(width, height), where 'width' and 'height' are your choices 32// for popup size, or you can display a button 'Sign in with Google' and set the 33//..'onclick' handler of the button to googleOpener.popup() 34 35var popupManager = {}; 36 37// Library constants 38 39popupManager.constants = { 40 'darkCover' : 'popupManager_darkCover_div', 41 'darkCoverStyle' : ['position:absolute;', 42 'top:0px;', 43 'left:0px;', 44 'padding-right:0px;', 45 'padding-bottom:0px;', 46 'background-color:#000000;', 47 'opacity:0.5;', //standard-compliant browsers 48 '-moz-opacity:0.5;', // old Mozilla 49 'filter:alpha(opacity=0.5);', // IE 50 'z-index:10000;', 51 'width:100%;', 52 'height:100%;' 53 ].join(''), 54 'openidSpec' : { 55 'identifier_select' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select', 56 'namespace2' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0' 57 } }; 58 59// Computes the size of the window contents. Returns a pair of 60// coordinates [width, height] which can be [0, 0] if it was not possible 61// to compute the values. 62popupManager.getWindowInnerSize = function() { 63 var width = 0; 64 var height = 0; 65 var elem = null; 66 if ('innerWidth' in window) { 67 // For non-IE 68 width = window.innerWidth; 69 height = window.innerHeight; 70 } else { 71 // For IE, 72 if (('BackCompat' === window.document.compatMode) 73 && ('body' in window.document)) { 74 elem = window.document.body; 75 } else if ('documentElement' in window.document) { 76 elem = window.document.documentElement; 77 } 78 if (elem !== null) { 79 width = elem.offsetWidth; 80 height = elem.offsetHeight; 81 } 82 } 83 return [width, height]; 84}; 85 86// Computes the coordinates of the parent window. 87// Gets the coordinates of the parent frame 88popupManager.getParentCoords = function() { 89 var width = 0; 90 var height = 0; 91 if ('screenLeft' in window) { 92 // IE-compatible variants 93 width = window.screenLeft; 94 height = window.screenTop; 95 } else if ('screenX' in window) { 96 // Firefox-compatible 97 width = window.screenX; 98 height = window.screenY; 99 } 100 return [width, height]; 101}; 102 103// Computes the coordinates of the new window, so as to center it 104// over the parent frame 105popupManager.getCenteredCoords = function(width, height) { 106 var parentSize = this.getWindowInnerSize(); 107 var parentPos = this.getParentCoords(); 108 var xPos = parentPos[0] + 109 Math.max(0, Math.floor((parentSize[0] - width) / 2)); 110 var yPos = parentPos[1] + 111 Math.max(0, Math.floor((parentSize[1] - height) / 2)); 112 return [xPos, yPos]; 113}; 114 115// A utility class, implements an onOpenHandler that darkens the screen 116// by overlaying it with a semi-transparent black layer. To use, ensure that 117// no screen element has a z-index at or above 10000. 118// This layer will be suppressed automatically after the screen closes. 119// 120// Note: If you want to perform other operations before opening the popup, but 121// also would like the screen to darken, you can define a custom handler 122// as such: 123// var myOnOpenHandler = function(inputs) { 124// .. do something 125// popupManager.darkenScreen(); 126// .. something else 127// }; 128// Then you pass myOnOpenHandler as input to the opener, as in: 129// var openidParams = {}; 130// openidParams.onOpenHandler = myOnOpenHandler; 131// ... other customizations 132// var myOpener = popupManager.createOpener(openidParams); 133popupManager.darkenScreen = function() { 134 var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); 135 if (!darkCover) { 136 darkCover = window.document.createElement('div'); 137 darkCover['id'] = window.popupManager.constants['darkCover']; 138 darkCover.setAttribute('style', window.popupManager.constants['darkCoverStyle']); 139 window.document.body.appendChild(darkCover); 140 } 141 darkCover.style.visibility = 'visible'; 142}; 143 144// Returns a an object that can open a popup window customized for an OP & RP. 145// to use you call var opener = popupManager.cretePopupOpener(openidParams); 146// and then you can assign the 'onclick' handler of a button to 147// opener.popup(width, height), where width and height are the values of the popup size; 148// 149// To use it, you would typically have code such as: 150// var myLoginCheckFunction = ... some AJAXy call or page refresh operation 151// that will cause the user to see the logged-in experience in the current page. 152// var openidParams = { realm : 'openid.realm', returnToUrl : 'openid.return_to', 153// opEndpoint : 'openid.op_endpoint', onCloseHandler : myLoginCheckFunction, 154// shouldEncodeUrls : 'true' (default) or 'false', extensions : myOpenIDExtensions }; 155// 156// Here extensions include any OpenID extensions that you support. For instance, 157// if you support Attribute Exchange v.1.0, you can say: 158// (Example for attribute exchange request for email and name, 159// assuming that shouldEncodeUrls = 'true':) 160// var myOpenIDExtensions = { 161// 'openid.ax.ns' : 'http://openid.net/srv/ax/1.0', 162// 'openid.ax.type.email' : 'http://axschema.org/contact/email', 163// 'openid.ax.type.name1' : 'http://axschema.org/namePerson/first', 164// 'openid.ax.type.name2' : 'http://axschema.org/namePerson/last', 165// 'openid.ax.required' : 'email,name1,name2' }; 166// Note that the 'ui' namespace is reserved by this library for the OpenID 167// UI extension, and that the mode 'popup' is automatically applied. 168// If you wish to make use of the 'language' feature of the OpenID UI extension 169// simply add the following entry (example assumes the language requested 170// is Swiss French: 171// var my OpenIDExtensions = { 172// ... // other extension parameters 173// 'openid.ui.language' : 'fr_CH', 174// ... }; 175popupManager.createPopupOpener = (function(openidParams) { 176 var interval_ = null; 177 var popupWindow_ = null; 178 var that = this; 179 var shouldEscape_ = ('shouldEncodeUrls' in openidParams) ? openidParams.shouldEncodeUrls : true; 180 var encodeIfRequested_ = function(url) { 181 return (shouldEscape_ ? encodeURIComponent(url) : url); 182 }; 183 var identifier_ = ('identifier' in openidParams) ? encodeIfRequested_(openidParams.identifier) : 184 this.constants.openidSpec.identifier_select; 185 var identity_ = ('identity' in openidParams) ? encodeIfRequested_(openidParams.identity) : 186 this.constants.openidSpec.identifier_select; 187 var openidNs_ = ('namespace' in openidParams) ? encodeIfRequested_(openidParams.namespace) : 188 this.constants.openidSpec.namespace2; 189 var onOpenHandler_ = (('onOpenHandler' in openidParams) && 190 ('function' === typeof(openidParams.onOpenHandler))) ? 191 openidParams.onOpenHandler : this.darkenScreen; 192 var onCloseHandler_ = (('onCloseHandler' in openidParams) && 193 ('function' === typeof(openidParams.onCloseHandler))) ? 194 openidParams.onCloseHandler : null; 195 var returnToUrl_ = ('returnToUrl' in openidParams) ? openidParams.returnToUrl : null; 196 var realm_ = ('realm' in openidParams) ? openidParams.realm : null; 197 var endpoint_ = ('opEndpoint' in openidParams) ? openidParams.opEndpoint : null; 198 var extensions_ = ('extensions' in openidParams) ? openidParams.extensions : null; 199 200 // processes key value pairs, escaping any input; 201 var keyValueConcat_ = function(keyValuePairs) { 202 var result = ""; 203 for (key in keyValuePairs) { 204 result += ['&', key, '=', encodeIfRequested_(keyValuePairs[key])].join(''); 205 } 206 return result; 207 }; 208 209 //Assembles the OpenID request from customizable parameters 210 var buildUrlToOpen_ = function() { 211 var connector = '&'; 212 var encodedUrl = null; 213 var urlToOpen = null; 214 if ((null === endpoint_) || (null === returnToUrl_)) { 215 return; 216 } 217 if (endpoint_.indexOf('?') === -1) { 218 connector = '?'; 219 } 220 encodedUrl = encodeIfRequested_(returnToUrl_); 221 urlToOpen = [ endpoint_, connector, 222 'openid.ns=', openidNs_, 223 '&openid.mode=checkid_setup', 224 '&openid.claimed_id=', identifier_, 225 '&openid.identity=', identity_, 226 '&openid.return_to=', encodedUrl ].join(''); 227 if (realm_ !== null) { 228 urlToOpen += "&openid.realm=" + encodeIfRequested_(realm_); 229 } 230 if (extensions_ !== null) { 231 urlToOpen += keyValueConcat_(extensions_); 232 } 233 urlToOpen += '&openid.ns.ui=' + encodeURIComponent( 234 'http://specs.openid.net/extensions/ui/1.0'); 235 urlToOpen += '&openid.ui.mode=popup'; 236 return urlToOpen; 237 }; 238 239 // Tests that the popup window has closed 240 var isPopupClosed_ = function() { 241 return (!popupWindow_ || popupWindow_.closed); 242 }; 243 244 // Check to perform at each execution of the timed loop. It also triggers 245 // the action that follows the closing of the popup 246 var waitForPopupClose_ = function() { 247 if (isPopupClosed_()) { 248 popupWindow_ = null; 249 var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']); 250 if (darkCover) { 251 darkCover.style.visibility = 'hidden'; 252 } 253 if (onCloseHandler_ !== null) { 254 onCloseHandler_(); 255 } 256 if ((null !== interval_)) { 257 window.clearInterval(interval_); 258 interval_ = null; 259 } 260 } 261 }; 262 263 return { 264 // Function that opens the window. 265 popup: function(width, height) { 266 var urlToOpen = buildUrlToOpen_(); 267 if (onOpenHandler_ !== null) { 268 onOpenHandler_(); 269 } 270 var coordinates = that.getCenteredCoords(width, height); 271 popupWindow_ = window.open(urlToOpen, "", 272 "width=" + width + ",height=" + height + 273 ",status=1,location=1,resizable=yes" + 274 ",left=" + coordinates[0] +",top=" + coordinates[1]); 275 interval_ = window.setInterval(waitForPopupClose_, 80); 276 return true; 277 } 278 }; 279}); 280