One option for securing ASP.NET pages that – IMHO – gets neglected quite often is the use of CodeAccessSecurity "Permission Attributes." You can read more about the general approach here.
Basically, they provide a very nice mechanism for encapsulating all of the rules you want to apply that will allow or disallow a particular user from viewing a page. You then drop the attribute onto the Page_Load event of the page you want to protect, and you’re done.
I thought I was going to need this approach to protect some page that were stored in the layouts section of a SharePoint site I was working with a while ago, but the need never materialized. I basically wanted to only allow access to these pages if the user was a member of a particular SharePoint group.
Here’s the approach I came up with.
First, I needed an IPermission implementation that would actually make the decision and enforce it. Here is that implementation (you’ll notice I left quite a lot unimplemented since I didn’t need it…):
public class SPUserPermission : IPermission
{
private string _role;
public SPUserPermission()
{
}
public SPUserPermission(string role)
{
_role = role;
}
#region IPermission Members
public void Demand()
{
bool success = false;
SPUtility.EnsureAuthentication();
SPUser spUser = SPContext.Current.Web.CurrentUser;
if (spUser != null)
{
foreach (SPGroup spGroup in spUser.Groups)
{
if (spGroup.Name.ToLower() == _role.ToLower())
{
success = true;
break;
}
}
}
if (!success)
{
SPUtility.HandleAccessDenied(new UnauthorizedAccessException());
}
}
#region un-implemented members
public IPermission Copy()
{
throw new NotImplementedException();
}
public IPermission Intersect(IPermission target)
{
throw new NotImplementedException();
}
public bool IsSubsetOf(IPermission target)
{
throw new NotImplementedException();
}
public IPermission Union(IPermission target)
{
throw new NotImplementedException();
}
#endregion
#region ISecurityEncodable Members
public void FromXml(SecurityElement e)
{
throw new NotImplementedException();
}
public SecurityElement ToXml()
{
throw new NotImplementedException();
}
#endregion
#endregion
}
Next I need an attribute that uses this implementation:
public class SPUserPermissionAttribute : CodeAccessSecurityAttribute
{
private string _role;
public SPUserPermissionAttribute(SecurityAction action)
: base(action)
{
}
public override IPermission CreatePermission()
{
return new SPUserPermission(this.Role);
}
public string Role
{
get
{
return _role;
}
set
{
_role = value;
}
}
}
You’ll notice that all this attribute does is create an instance of my IPermission implementation and pass it the Role property that’s passed into the attribute. What happens next is determined by how I use this attribute on my pages:
[SPUserPermission(SecurityAction.Demand, Role = "Foo")]
protected void Page_Load(object sender, EventArgs e)
{
}
When Page_Load fires, this attribute must fire first. It will initialize my SPUserPermissionAttribute and pass through the action that I want – Demand, and the Role that I want – Foo. This will cause my IPermission implementation’s Role property to be set and its Demand() method to fire. That will grab the CurrentUser from SPContext and iterate its Groups looking for my Role propery value. If it can’t find it, SPUtility.HandleAccessDenied will be called and you’ll find yourself on SharePoint’s Access Denied page.
I’m relatively sure that this approach would work out nicely. However, some caveats apply since this never ended up making it into a production system. Hopefully someone will find this useful.