Custom security trimming in ASP.NET sitemaps
In ASP.NET you can create a sitemap to capture the pages that are available in your web application. The web.sitemap is an XML file that is a hierarchical list that is provided for data binding to controls via a sitemap provider. The Menu, SiteMapPath and TreeView control are common controls used for the binding to the sitemap data.
The fragment shows a bit of a web.sitemap.
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" enableLocalization="true">
<siteMapNode title="Root" description="My Links" roles="*">
<siteMapNode url="~/Default.aspx" title="Home" resourceKey="Home" description="Go to homepage" roles="*"/>
<siteMapNode url="~/Admin/EditPosting.aspx" title="Add Entry" resourceKey="AddEntry" description="Adds a new posting"
roles="BlogContributor;BlogOwner;BlogAdmin;BlogEditor" />
The default provider XmlSiteMapProvider has a built-in mechanism to trim the sitemap based on roles of an authenticated user. In the fragment you can see the roles attribute that lists the roles an authenticated user should have to "see" the corresponding siteMapNode. When everyone is allowed access you simply specify * as the role.
The security trimming from .NET's Role Based Security (RBS) is all nice and easy if you use roles in your security. But what if you use application rights instead of roles to validate access to parts of your app? You will need to build your own custom sitemap provider. This is a fairly trivial task. Derive your custom provider class from XmlSiteMapProvider if you think that the XML files like web.sitemap is your thing. Otherwise you can derive from the abstract base class SiteMapProvider and role your own provider from scratch. I assume that you are OK with the XML files.
namespace KillerApps.Web
{
public class CustomSiteMapProvider: XmlSiteMapProvider
{ ... }
}
The key to creating custom security trimming is an override to the IsAccessibleToUser method. This method gives you two arguments for the current HTTP context of the request and the node to evaluate. This node is found by the base class after a lookup of the current URL. In the override we determine whether the current user has access to the given node.
public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
{
// Security trimming should be enabled, otherwise everything is accessible
if (!this.SecurityTrimmingEnabled)
return true;
// Default to no access when no rights are specified
if (node["rights"] == null) return false;
if (node["rights"] == "*") return true;
// Split on separator and check if items exist
string[] rights = node["rights"].Split(';', ',');
if (rights == null || rights.Length == 0) return false;
// Check each right against security manager
foreach (string right in rights)
{
if (SecurityManager.IsAuthorized(context.User, right))
{
return true;
}
}
return false;
}
This sample shows a possible implementation of the override IsAccessibleToUser. First it checks if security trimming is enabled and continues to check what if rights are defined. Note the bold items: we'll get to that in a minute. Next the rights are checked against your own security implementation. The SecurityManager mentioned is not part of the .NET Framework.
The bold items show a great extensibility point of sitemaps. You can define any attribute on a <siteMapNode>, even if it is not in the XML schema for sitemaps. Any attribute is available through the indexer property of the SiteMapNode class. The bold items show how you can read attributes from the sitemap nodes when defined like so:
<siteMapNode url="~/Default.aspx" title="Home" resourceKey="Home" description="Go to homepage" rights="ViewHomePage" />
Side note: the XSD file for the sitemaps incorrectly lists the securityTrimmingEnabled attribute as part of <siteMapNode>. This is not correct, because this is an attribute in the web.config. Your web.config should contain a definition for the custom provider:
<siteMap enabled="true" defaultProvider="CustomProvider">
<providers>
<add name="CustomProvider" type="KillerApps.Web.CustomSiteMapProvider"
description="Rights based security trimmed sitemap provider"
siteMapFile="~/web.sitemap" securityTrimmingEnabled="true" />
</providers>
</siteMap>
Notice how you can also specify another file as the sitemap. It is not uncommon to have multiple providers that offer sitemaps from different files.