Jeff Garoutte

c# .net and anything else that happens across my desk

Extending the ASP.Net Security model to use rights : Part one - IPrincipal

Every now and again I find myself disappointed in the asp.net security model.  The ability to assign roles is useful but if a role changes and I have implemented code security by role I now have to alter my PrincipalAttributes.  That isn't a huge issue, but I am not of a fan of recompiling my code because the of a minor change when I could secure the code by a right and assign a right to a role in a data store.

The first thing we need is a right

using System;

namespace ObjectHelpDesk.Security
{
    public class Right
    {
        private Guid _id = Guid.Empty;
        public Guid Id
        {
            get { return _id; }
            set { _id = value; }
        }

        private String _rightName = String.Empty;
        public String RightName
        {
            get { return _rightName; }
            set { _rightName = value; }
        }   
    }
}

Nothing to exciting there, its a simple little object.    It is however the IPrincipal the is the workhorse of the asp.net security model.  Now that we have a right to extend the security system with we need an IPrincipal for asp.net to pass around.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;

namespace ObjectHelpDesk.Security
{
    public class RightPrincipal : IPrincipal
    {
        public RightPrincipal(IPrincipal user)
        {
            _user = user;
        }

        public RightPrincipal(IPrincipal user, List<Right> rights)
            : this(user)
        {
            _rights = rights;
        }

        private IPrincipal _user = null;
        private List<Right> _rights = new List<Right>();

        public bool HasRight(String rightName)
        {
            bool result = false;
            if (_user is RightPrincipal)
            {
                result = ((RightPrincipal)_user).HasRight(rightName);
            }
            
            if (!result)
            {
                result = _rights.Count(r => r.RightName == rightName) > 0;
            }

            return result;
        }

        #region IPrincipal Members

        public IIdentity Identity
        {
            get { return _user.Identity; }
        }

        public bool IsInRole(string role)
        {
            return _user.IsInRole(role);
        }

        #endregion
    }
}

There are a few things to talk about here.  There are two constructors, both take an IPrincipal object.  We are going to need to create an IHTTPModule later that builds our custom RightPrincipal and the it will pass the existing asp.net IPrincipal object into the constructor.

We store existing IPrincipal in a private field and use it to get the Identity object and implement the IsInRole function.  We also create a list of List<Right> and a HasRight method.  Has right checks the existing IPrincipal to see if it is a RightPrincipal and calls has right on it if it is.  If the IPrincipal is not a RightPrincipal or the right is not found it does a quick little check to see if the Right exists in the RightPrincipal's collection of rights.

We now have Right and a custom IPrincipal, but how do we attach them to the current request without doing something in every page?

Up next - Part 2: the IHttpModule

kick it on DotNetKicks.com

Posted: Apr 27 2008, 11:29 by jeff | Comments (20) RSS comment feed |
Filed under: Security
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Comments

HP said:

HP

Hi Jeff
Thanks for the example, I haven't seen anything similar on the net despite the approach is obviously way more more flexible than hardcoding the roles.
But what about authorization paths other than securing code execution, like URL Authorization or authorization support in controls like Site Map - do you have any ideas how could one switch to Rights based auth and not loose all this features?

thanks
HP

# July 25 2008, 21:49

Jeff United States said:

Jeff

I have considered this in the past and it is on my to do list...

Enabling a site map to be able to do security trimming based on roles would need a sub class of the SiteMapNode class that adds a rights collection and a subclassed SiteMapProvider that overrides public virtual bool IsAccessibleToUser(HttpContext context, SiteMapNode node).  

Personally I think Microsoft goofed here.  The code to check is the node is accessible to a user should have lived in the node itself.  This way overriding the node and making sure it was populated would have been enough to get it to work.

I havent looked into extenteding the FormsAuthencationModule to include right based url authorization but it would be easy to add to the HttpModule a url to right check and throw the security error (or alter the response headers).  The hard part would be managing the URL to rights but creating a web.config area to manage it should not be diffcult.  

In theory, it may be possible to replace the default FormsAuthencationModule with a custom FormRightsAuthencationModule.  However, from what I remember seeing in the framework when I looked into it, that is not a small task as FormRightsAuthencationModule is a sealed class the inherits IHttpModule and does not have an interface and the module is activated by the "mode" setting of the authentication section in the web.config...

AuthenticationSection is a good starting point to look at...
msdn.microsoft.com/.../...ationsection(VS.80).aspx

Happy coding!


# July 26 2008, 21:28

JJ said:

JJ

Can I use standard form authentication with this custom authorization? The problem is that on each postAuthenticate event Context.User is GenericIdentity type, despite that it was overriden with custom identity on a previous request..

# August 04 2008, 23:29

JJ said:

JJ

I meant GenericPrincipal type. So on each request the RightPrincipal object should be recreated (along with fetching rights from db) or am I doing something wrong, and after setting context.user to RightPrincipal once after authentication it should stay right there in a user context until he logs out?

many thanks
JJ

# August 04 2008, 23:39

Jeff United States said:

Jeff

JJ,
Yes it works with standard forms authenication; That's pretty much all I use.  

The principle has to be created on every request.  

Check out part two. www.jeffgaroutte.net/.../...--the-IHttpModule.aspx
If you have already added the HttpModule and configered it in the web.config you might want to double check the order of the HttpModules.  Make sure the HttpModule that switches the Principal is running before the code you are having an issue with.  

Let me know how it goes,
-Jeff

# August 05 2008, 10:04

JJ said:

JJ

Hi Jeff
It's clear now. The only problem I see is that postAuthenticate event seems to fire 2 or 3 times in a request (eg. after logging in I step into it with a debugger once when user.authenticated is false, and immediately with user.authenticated == true) - do you have any idea why?

Thanks a lot

# August 05 2008, 13:42

Jeff United States said:

Jeff

Most likely, it isnt the same request.  When visual studio runs in debug mode it uses Cassini.  Cassini is a "light" version of IIS.  There are several key differences between them.  One notable difference is the type of worker process they use which often bites people in the rear when they try and use reflection to change form post values (there is code about uploading large files with a status indicator out there that suffers this very problem).

The other key difference, and the one I suspect you are seeing, is a subtle and annoying difference between the default configuration of Cassini and IIS.

Because Cassini is not IIS it has to handle every request, not just aspx files, but css, gif, jpg, png, js, swf, ...etc etc.  

IIS by defualt only passes a few file extenisons to the .net framework...asp, asmx...etc etc...

Here's the kick in the teeth, when someone requests default.aspx that isnt really 1 request.  It's just the first one.  The browser sees extrenal files, like a background image or css file, and requests them from the server.

IIS sees the request, pass the request to an isapi filter (or application) based on the file type and spits out the response from the isapi.

Cassini sees the request and, regradless of file type, spits out a response from asp.net.  You can think of Cassini as IIS with only 1 isapi application install, asp.net.  

when you're debugging, have a look at the url the module says it's processing.

You can add a check on the file extension and bypass the the code if it is not a type you were expecting.  If you do this, make sure you get all the asp.net file extensions otherwise you'll find yourself wondering why your principal is not what it should be in your web service... or something.

# August 05 2008, 17:50

JJ said:

JJ

Jeff

You were right - it was about the external resources, so it's not a problem.

By the way, are you fetching rights from DB on each request without caching it? I'm asking because there are problems with accessing Session object from within httpModule - have you try it?

cheers

# August 05 2008, 19:03

Jeff United States said:

Jeff

I try to handle chaching of data in the datalayer (with SQL I use a SQLDepencencyCache?sp? object).  This allows me to cache data in a way that fits the lifecycle of the data that is best suited to the data store.

Normally, this would be inside of a provider.

# August 05 2008, 22:50

JJ said:

JJ

Jeff

One more thing: why are you concerned about possibility of wrapping the standard principal twice with the rightPrincipal? Is it just in a case, or you had some real scenario in mind?

I'm talking about if (_user is RightPrincipal) and some other place that I don't currently remember.

thanks

# August 06 2008, 19:53

Jeff United States said:

Jeff

Yes there is a reason. As you have already noticed, it pulls from the datasource on every request. If it is already a RightPrincipal it doesnt need to be one again. More importantly the call into the data store does not need to happen again.

There was a case where a site had several security modules in place; the same custom principal class was being loaded with different values out of different providers depending on a logic tree that had...well...no logic and it "shouldnt replaced it if it already existed. Really they should have been rewritten into one module.

While rare, it did point out that assuming the type of object can be a bad idea and that it might already have been processed. Why process it a 2nd time?

So old habit, error on the safe side in case someone does something silly.

# August 07 2008, 08:55

Jason People's Republic of China said:

Jason

Great Article!It's really help me a lot!

# September 16 2008, 22:20

Brad United States said:

Brad

This may seem like a dumb question - but I'm new so I don't have a choice. Laughing

I see that RightsHttpModule replaces the current principal (HttpContent.Current.User) with the new RightPrincipal.

I would like to call RightPrincipal's HasRight() method from a page, but get an "IPrincipal does not contain a definition for HasRight" error.  If HttpContent.Current.User is now a RightPrincipal, why can I not call HasRight()?  (i.e. HttpContent.Current.User.HasRight(myRight))

# February 05 2009, 16:48

Jeff United States said:

Jeff

@Brad

You have to type cast the IPrincipal.

something like...

if((HttpContent.Current.User is RightPrincipal) &&((RightPrincipal)HttpContent.Current.User).HasRight("myRight"))
{
//they have it
}
else
{
//they dont or are not a right principal
}

# February 20 2009, 14:18

tipsonlips.net said:

pingback

Pingback from tipsonlips.net

ASP.NET Membefrship Resources

# March 31 2009, 00:24

ixtapa photos United States said:

ixtapa photos

The book seems so interesting and content of the post is very valuable.Thanks for sharing this post.



Regards
Seals

# December 17 2009, 00:25

how to cure snoring United States said:

how to cure snoring

Great post! I am just starting out in community management/marketing media and trying to learn how to do it well - resources like this article are incredibly helpful. As our company is based in the US, it?s all a bit new to us. The example above is something that I worry about as well, how to show your own genuine enthusiasm and share the fact that your product is useful in that case.

# January 06 2010, 23:45

Fatcow Review United States said:

Fatcow Review

Should I get a Dedicated Hosting?  I am using anhosting but they keep disabling my account because of high server overload. Im getting about 3,000 UV a day. What hosting should I get?

# March 01 2010, 11:35

wow mobiles United States said:

wow mobiles

WoW Mobile is awesome! I get free mobile service with t-mobile because I refered 3 people to wow. You can too!

# March 03 2010, 04:06

wow mobile United States said:

wow mobile

WoW Mobiles is awesome! I get free mobile service with t-mobile because I refered 3 people to wow. You can too!

# March 03 2010, 06:21

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
Loading