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 35 var popupManager = {}; 36 37 // Library constants 38 39 popupManager.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. 62 popupManager.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 88 popupManager.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 105 popupManager.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); 133 popupManager.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 // ... }; 175 popupManager.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