// 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;
}
};
});