WSS and audience targeting - Part II
Today is NOT my day for things just working. Besides the demo gods eating every demo I did today and presenting me with excrement to work with, I found out that the super cool WSS audience targeting is broken in every browser except IE. There is no use in complaining, so the silver lining is that I do get a chance to solve the problem and learn something new about browsers.
First off, XmlHttpRequest is supported by all browsers, but the XML DOM implementation is not compatible—so even though you can get the data, you can’t work with it easily. The cause? Firefox and Chrome do not support the _selectNodes or _selectSingleNodes methods, while IE has unique methods that no other browser supports (depending on which camp you're in). _selectSingleNode is what I use to parse my results! To solve this, I found some code at http://km0.la/js/mozXPath/, which adds the methods to the JavaScript classes. This was great in theory, but it didn’t work because my XML had a default namespace being returned. The code didn’t work as selectSingleNode always returned null because it needed the namespace prefix on the XPath (e.g., /default:user), which IE can’t handle. So, that meant not only implementing a new resolver—which I found out about on developer.mozilla.org—but also doing a check and running two XPath queries.
Something I didn’t mention in the first post is security—and how secure or insecure this method is. Your biggest attack vector is that it runs client-side, and methods like LoadXmlDoc or even a length check on the username can be easily modified to show content for logged-in users to users who are not logged in. Basically, it is not secure, but that doesn’t mean it opened a security hole—you need to think about what is shown. In my case, I made sure only the bare minimum was displayed: links to other pages. If an attacker somehow gets these links, it doesn’t worry me because the change-password page—which only signed-in users can access—is protected by WSS’s security. So even if someone were to try accessing it, they would be denied! The page itself also has security measures in place to help prevent issues. The point I want to emphasize is that this is not a security model, but rather a model for a better user interface.
<script type="text/javascript">
// Created by RMacLean - for comments email drpsupport@bbd.co.za
// Partially from http://www.w3schools.com/XML/xml_http.asp
// Partially from http://en.wikipedia.org/wiki/Xmlhttprequest
// Partially from http://km0.la/js/mozXPath/
// Partially from http://developer.mozilla.org/en/Introduction_to_using_XPath_in_JavaScript#Implementing_a_User_Defined_Namespace_Resolver
// check for XPath implementation
if (document.implementation.hasFeature("XPath", "3.0")) {
// prototyping the XMLDocument.selectNodes
XMLDocument.prototype.selectNodes = function(cXPathString, xNode) {
if (!xNode) { xNode = this; }
var oNSResolver = document.createNSResolver(this.ownerDocument == null ? this.documentElement : this.ownerDocument.documentElement);
function resolver() {
return 'http://schemas.saarchitect.net/ajax/2008/09/user';
}
var aItems = this.evaluate(cXPathString, xNode, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var aResult = [];
for (var i = 0; i < aItems.snapshotLength; i++) {
aResult[i] = aItems.snapshotItem(i);
}
return aResult;
}
// prototyping the Element
Element.prototype.selectNodes = function(cXPathString) {
if (this.ownerDocument.selectNodes) {
return this.ownerDocument.selectNodes(cXPathString, this);
}
else { throw "For XML Elements Only"; }
}
// prototyping the XMLDocument.selectSingleNode
XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode) {
if (!xNode) { xNode = this; }
var xItems = this.selectNodes(cXPathString, xNode);
if (xItems.length > 0) {
return xItems[0];
}
else {
return null;
}
}
// prototyping the Element
Element.prototype.selectSingleNode = function(cXPathString) {
if (this.ownerDocument.selectSingleNode) {
return this.ownerDocument.selectSingleNode(cXPathString, this);
}
else { throw "For XML Elements Only"; }
};
}
// Provide the XMLHttpRequest class for IE 5.x-6.x:
if (typeof XMLHttpRequest == "undefined") XMLHttpRequest = function() {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) { }
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) { }
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { }
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { }
throw new Error("This browser does not support XMLHttpRequest.");
};
var xmlhttp;
function loadXMLDoc(url) {
xmlhttp = new XMLHttpRequest();
if (xmlhttp != null) {
xmlhttp.onreadystatechange = state_Change;
xmlhttp.open("GET", url, true);
xmlhttp.send(null);
}
else {
alert("Your browser does not support XMLHTTP.");
}
}
function state_Change() {
if (xmlhttp.readyState == 4) {// 4 = "loaded"
if (xmlhttp.status == 200) {// 200 = OK
var username = "";
if (xmlhttp.responseXML.selectSingleNode('//user') == null) {
username = xmlhttp.responseXML.selectSingleNode('//myns:user').getAttribute('username');
}
else {
username = xmlhttp.responseXML.selectSingleNode('//user').getAttribute('username');
}
if (username.length > 0) {
// user logged in
document.getElementById('resultText').innerHTML = '<P><A href="/memberPages/changepassword.aspx">Change Password</A></P>';
}
else {
// anonymous
document.getElementById('resultText').innerHTML = '<P><A href="/Pages/signup.aspx">Signup</A><BR><A href="/Pages/forgotpassword.aspx">Lost Password</A></P>';
}
}
else {
alert("Problem retrieving XML data");
}
}
}
loadXMLDoc("/Pages/loggedinuser.aspx");
</script>
<span id="resultText">Loading...</span>