Jeff Garoutte

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

Recent posts

Tags

Categories

Navigation

Pages

    Archive

    Blogroll

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    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 (12) RSS comment feed |
    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5
    Filed under: Security

    Related posts

    Comments

    HP said:

    HPHi 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 24 2008, 18:49

    Jeff us said:

    JeffI 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/.../...on.authenticationsection(VS.80).aspx

    Happy coding!


    # July 25 2008, 18:28

    JJ said:

    JJCan 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 03 2008, 20:29

    JJ said:

    JJI 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 03 2008, 20:39

    Jeff us said:

    JeffJJ,
    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 04 2008, 07:04

    JJ said:

    JJHi 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 04 2008, 10:42

    Jeff us said:

    JeffMost 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 04 2008, 14:50

    JJ said:

    JJJeff

    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 04 2008, 16:03

    Jeff us said:

    JeffI 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 04 2008, 19:50

    JJ said:

    JJJeff

    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 05 2008, 16:53

    Jeff us said:

    JeffYes 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 06 2008, 05:55

    Jason cn said:

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

    # September 15 2008, 19:20

    Add comment


    (Will show your Gravatar icon)  

      Country flag

    [b][/b] - [i][/i] - [u][/u]- [quote][/quote]