Click here to Skip to main content
15,881,248 members
Articles / Web Development / ASP.NET

Web-Application Framework - Catharsis - part III - Access rights, Navigation

Rate me:
Please Sign up or sign in to vote.
4.78/5 (4 votes)
18 Mar 2009CPOL14 min read 42K   33   2
Catharsis 1.0 web-app framework - Access rights, Navigation

Catharsis 1.0 part III - Access right management, Navigation

This article is the third step in a Catharsis documented tutorial. Catharsis is web-application framework gathering best-practices, using ASP.NET MVC 1.0, NHibernate 2.0.1. All needed source code you can find on   

http://www.codeplex.com/Catharsis/

Catharsis-title.png

No.ChapterNo.ChapterNo.Chapter

I New solution VI CatharsisArchitectureXI Controller Layer

II Home page observationVII Entity layer XII (UI Web layer - undone)

III Access rights,
Navigation
VIII Data layer XIII Tracking, PAX design pattern

IV Localization, translationsIX Business layerXIV Catharsis Dependency Injection (DI, IoC)

V Enter into Catharsis, new EntityX Model layer XV (Code Lists - undone)

Catharsis 1.0 

Let me proudly announce the first alpha release of Catharsis 1.0

This web-app Framework now covers all layers, provides basic recipes and uses the full power of the ASP.NET 1.0! There is also move to NHibernate 2.1 alpha, using its Castle proxies (The LinFu was making troubles in this alpha release, maybe in next it will replace Castle). You can now create Entities, CodeList values and light version of ICodeList. There is also smart “lazy-column-trick” for list display, which can be found in the search.PropertyList (Do you have large entity with columns containing files and large texts?, for list-view fill only the search.PropertyList and the SQL Select clause will use only these properties – very powerful and performance increasing). Messages now also provides direct logging using Log4Net and are more break-proctective (even if the incorrect format strings are provided, e.g. “Message 0}” instead of “Message {0}”, this is the must, because localized versions come from users)

But first of all, this is the real Catharsis 1.0, covering all layers, all pieces (rights, menus, smart ajax…) and finally based on ASP.NET MVC 1.0. The almost year effort now brings you the sweet fruit.  

Enjoy Catharsis 1.0

Access right management, Navigation  

Catharsis 1.0 (and higher?). This article was entirely rewritten to fully describe abilities of the version 1.0. And with small adjustments and extension it should be stable for all future generations.

Access right management

Access-rights do not belong to the MVC layers. In fact, rights are separated layer itself, dependent on different application needs (web part, batch importer etc.).
Separation of concern for access-rights” is crucial for future application maintenance and extension. But as experience speaks, this is a bit (in fact very) difficult to understand for developers coming from any pre-AOP world. Their need to enjoy hard-coding on every Page_Onload is hard to change...

Application in a Crystal view – 3D Matrix.

Imagine your application as an adjustable and flexible 3D matrix. It could be disassembled and described as nodes and links. These nodes are entities (Person, Subject, CodeList) and links are all the CRUD operations (search & list, add, updated, delete). Entities and their CRUD operations are maintained similarly and therefore could be seen as a node and links if the 3D matrix (only as an example).

Catharsis will help you in this way. Every entity will have these operations - by default. Whenever you append new entity using the Guidance, you are provided with implemented ‘100% functional’ CRUD on all layers. Your next steps are ‘only’ to apply business checks (can be deleted? Are key properties provided? Is the new item unique? Extend searching, etc.)

Why the 3D matrix: because this crystal view has no dependency on rights and roles. In fact every operation is possible for any entity (if the business rules are met)

Filter the Crystal view

Users interact with application in the Actor roles. Administrator, Translator, Viewer – each needs different entities (nodes) and operations (links) to be accessible.
The 3D matrix example will resume in the semi filled matrix, where due to applied Actor-role-filter are some nodes and links removed (in fact hidden). The Filter is the right word. It could be adjusted, changed or not applied at all. But anyhow it is the separate layer, the AOP filter. Application still has all these nodes and links; they are not removed – when the filter is applied.
This approach will at the end allow Customer to decide who (Actor role) has the rights for which part (entity) and can use which action (e.g. only search & list for Viewer). And what’s more, this could (and will) be applied even during the application production deployment.
The benefit is awesome.
You can develop your application as there are no roles (create the full 3D matrix).
Than (when everything is done) append restrictions using the separate layer (access rights management) to meet your Customer needs.
And that’s Catharsis, that’s separation of concern in the essential form.
(I’ve tried to create some easy to read pictures with 3D Matrix, but I failed. Anyway, I do believe that this comparison was not confusing and that you’re still on the track. )

AOP

AOP (aspect oriented programming) – is one of the ASP.NET MVC built-in fundamental features. The C-layer (controller) allows you to decorate any controller’s action with attributes. They should implement some of the filter interface (e.g. IAuthorizationFilter) to be called properly by ASP.NET MVC framework.
Whenever an action is called every AOP filter comes into play and its before or/and after operations are called properly. Every means that ASP.NET MVC calls all filters which decorate: the action itself, its controller, and even the bases (base controllers or action ancestor if overridden).

Separation of concern

The Catharsis framework offers easy to use solution. It is based on a few elements for access rights management. If you’ll think about similar approach on another existing project, you can reuse only these small parts (classes):
The filter RoleAccessAttribute (AOP)
The XML reader called NavigationMenuProvider (the name comes from its secondary use, see below)
The access-rights-definition file Menu.config.
That’s all. Append it to your application and decorate all your base controllers with [RoleAccess] attribute.
Decorate base controllers with RoleAccess filter attribute
Adjust the Menu.config settings and you’re done. Let’s see how it works in a more detail.

Menu.config

There is one XML configuration file called Menu.config. This is in fact the central and only point for your access right management. (You can distribute it as a plain xml or an embedded resource with some adjustments, but nevertheless – roles are separated and other layers independent)

XML
<?xml version='1.0' encoding='utf-8' ?>
<menu xmlns='http://ProjectBase/Config.xsd' >

    <!-- CodeLists -->
    <controllerOrNode name='CodeLists' >
        <roleGranted name='Administrator' />
        <roleGranted name='RegisteredUser' />
        <roleGranted name='Viewer' isReadOnly='true' />

        <!-- Country -->
        <controllerOrNode name='Country' parent='CodeLists' >         
          <roleGranted name='Administrator' />
          <roleGranted name='RegisteredUser' />
          <roleGranted name='Viewer' isReadOnly='true' />
        </controllerOrNode>
    ...
</controllerOrNode>
</menu> 

No alchemy, you’ll see

controllerOrNode & roleGranted elements

Root element “menu” can contain child elements “controllerOrNode”.
Node is only a hierarchical element and has no impact on right management. It only allows nest group of controllers to one parent node e.g. CodeLists parent for Country, Currency, ContentType
Controller on the other hand is the gateway to MVC layer.
In the above snippet you can see that Country controller is accessible for roles RegisteredUser and Viewer. This is done via “roleGranted” elements, connected to application roles using attribute name=”RoleName”. Any not mention role is denied.
There is also interesting switch isReadOnly with some crucial impacts.

  1. IMasterModel.IsReadOnly is set to true (and you can use this information on other places – independently on access rights)
  2. [Transaction] attribute, which Commits the “NHibernate data changes” to the database, will always RollBack the transaction if the IMasterModel.IsReadOnly.
  3. Every controller’s action decorated with [Transaction] attribute is prohibited
  4. Every Action or Button stored in the IMasterModel.ActionsModel & IMasterModel.ButtonsModel and decorated with [Transaction] attribute is removed (why to show them, if they are prohibited anyway).

One small step for controller, but the giant impact for application.

Granular settings with action attribute

For some purposes could be handy if you can set only some actions for specific controller to be granted. Or prohibited. Therefore every element “roleGranted” can contain the list of “action” elements with the self explaining “name” attribute.

XML
<roleGranted name='Anonym' accessForListed='grant'>
    <action name='List'/>
</roleGranted>

As you can see role Anonym in this example has granted access to the action List. Any other action of this controller is forbidden.
You can also turn the list behavior by setting the accessForListed="deny". It will grant access to all actions of that controller except the listed.

WriteActionList.config

There is a complementary list for Menu.config, the WriteActionList.config file.

XML
<?xml version='1.0' encoding='utf-8' ?>
<menu xmlns='http://ProjectBase/Config.xsd' >
    <writeActionList>
        <action name='New'/>
        <action name='Edit'/>
        ...      
    </writeActionList>
</menu>

Catharsis creates its own WriteActionList by reflection of the C-layer (controllers.dll). Every action which is decorated with [Transaction] attribute is handled as Write action.
But there are some actions that only act as a pre-steps for these write actions. For example the “New” action which gathers the form information and than calls the “Add” action. “New” action does not use transaction because does not store any changes. The check would be applied only on the next step “Add”.
But why should we show and provide the “New” action to someone (e.g. Viewer) who does not have the access to “Add”. To avoid these situations, there is the WriteActionList.config. Put any other action you do not want to allow or show when the IsReadOnly switch is turn on.

AppRoleProvider

As we’ve already seen above, the access rights setting for every role is trivial. In the Catharsis framework you are only adjusting Menu.config file.
And where do the roles which are evaluated come from?
RoleAccess filter attribute asks the roleProvider encapsulated in the ApplicationRoleProvider object. This static instance is creates ASP.NET runtime using standard roleProviders setting in the web.config section:

XML
<roleManager enabled='true' defaultProvider='AppRoleProvider'>
  <providers>
    <clear />
    <add name='AppRoleProvider' applicationName='Firm.Product.Web' 
        type='Firm.Product.Common.Providers.AppRoleProvider, Firm.Product.Common' />
            
            <!-- !!! do not remove this provider -->
            <!-- 
                when your own provider is set as the defaultProvider e.g. 
                    <roleManager enabled='true' defaultProvider='LoginProvider'>
                CommonProvider will act as an ApplicationRole provider using
                    your 'LoginProvider' implementation.
                * LoginProvider will simply implement .NET RoleProvider abstract members
                * CommonProvider will extended that 'default provider' 
                             with IAppRoleProvider interface
                    
                    ... very easy to use ... out of the box ... -->
    <add name='CommonProvider' applicationName='Firm.Product.Web'
        type='Firm.Product.Common.Providers.CommonProvider, Firm.Product.Common' />
  </providers>        
</roleManager>

There are already two role providers existing in Catharsis: AppRoleProvider and CommonProvider.

AppRoleProvider

AppRoleProvider is inherited from WindowsTokenRoleProvider provider and uses Catharsis entities AppRole and AppUser. Any user is authenticated as domain\user and then AppRoleProvider reads its identification from application database (object AppUser and its set of AppRoles).
AppRoleProvider allows you to manage the users in the application using its interface (all standard CRUD operations). This is very handy for intranet scenarios and applications which needs user’s entities for other entity handling (e.g. Subjects are managed by unique AppUser)

CurrentRole

The Catharsis framework introduces another role based feature – CurrentRole.
AppRoleProvider is derived from RoleProvider (due its base WindowsTokenRoleProvider) and that’s why it can be used as an ASP.NET provider. Such a RoleProviders in common is able to answer question “IsUserInRole()”, which leads to mishmash for users with many roles. These users (with many roles) are provided with almost every application features, regardless if they need them in current scenario or not.

Change the CurrentRole management. Admin has its own set of navi links
To explicitly show the disadvantage, let’s use the Actor approach view. Actors are accessing our application in different scenarios with different needs. E.g. ‘FormDesigner’ creates forms to be filled by ‘Evaluator’ and viewed by ‘Assessed’. But some person can play more then one role (e.g. personnel department manager) and we simply have to distinguish it.
Our application cannot confuse user’s by providing all actions and entities using “IsUserInRole()” or “GetRolesForUser()
Solution provided by Catharsis is simple and efficient. One of the roles provided by “GetRolesForUser()” is simply converted into the CurrentRole. The UI then fits needs of selected role – Actor role. Nothing else is displayed then the Actor’s selected filter needs.
If user needs to change CurrentRole, and to play as another Actor - there is the CurrentRole switch. It’s displayed on the master page in the left bottom corner or as an action of the HomeController. CurrentRole is than persisted in the database and reused in the next session.

IAppRoleProvider

To allow this behavior and management there is en extension to ASP.NET abstract RoleProvider interface (interface provided by abstract class, shame it is not an interface!)
The IAppRoleProvider interface:

C#
interface IAppRoleProvider
{
    AppUser User { get; }
    IList<AppRole> GetRolesForCurrentUser();
    AppUser ChangeCurrentRole(AppRole role);
    AppUser ChangeCurrentRole(string roleName);

    // ASP.NET RoleProvider abstract stuff redeclared as an interface
    string ApplicationName { get; set; }
    void CreateRole(string roleName);
    bool DeleteRole(string roleName, bool throwOnPopulatedRole);
    string[] FindUsersInRole(string roleName, string loginToMatch);
    void AddUsersToRoles(string[] logins, string[] roleNames);
    string[] GetAllRoles();
    string[] GetRolesForUser(string userName);
    string[] GetUsersInRole(string roleName);
    bool IsUserInRole(string userName, string roleName);
    bool IsUserInRole(string roleName);
    void RemoveUsersFromRoles(string[] usernames, string[] roleNames);
    bool RoleExists(string roleName);
}

All this functionality is exposed by static AppRoleProvider instance accessible via ApplicationRoles (similar to ASP.NET Roles).

  1. RoleAccess filter asks ApplicationRoles.CurrentRole
  2. RoleAccess filter next reads the Menu.config to know the rights for this role
  3. RoleAccess filter grants access or denies it – the action is canceled and user is provided with this sad information.
  4. Information for not authorized access - change CurrenRole if possible

CommonProvider

The web.config roleProvider setting contains also the CommonProvider. Its purpose is to bridge your own independent RoleProvider to IAppRoleProvider. In fact it will be very very handy in scenarios you are using some common e.g. AcitveDirectoryRoleProvider.
How to do it? Append your RoleProvider definition into the web.config providers section and mark it as defaultProvider. And don’t remove CommonProvider.
From this moment your ActiveDirectoryRoleProvider will provide GetRolesForUser() etc. CommonProvider will extende with the CurrentRole stuff and cooperate with the ApplicationRoles. User’s will be authenticated with your provider, will be able to select one of their roles as the CurrentRole and then act with application using authorization based on Menu.config role settings.
If your external provider returns strange strings instead of “nice” roleNames e.g. GF_BR_ADMIN1 instead of Administrator you can extend the Str.Roles constructor and adjust appSetings to create new mapping. Application gains another plus – separation of the external Role provider naming “quasi-standards” …

XML
<appSettings>        
        <!-- allows to specify default list page rows count -->
        <add key='DefaultListRowCount' value='25'/>
        ...
        <!-- external to internal roles mapping -->
        <add key='Administrator' value='GF_BR_ADMIN1 ' />
        ...
</appSettings>

And adjust the constants in Common project

C#
public partial class Str : Constants
{
    public partial class Roles
    {
        protected Roles() { }
        public const string Administrator = "Administrator";
        public const string Translator = "Translator";
        public const string Viewer = "Viewer";
        public const string RegisteredUser = "RegisteredUser";
        public const string Anonym = "Anonym";

        public static readonly IList<string> RoleList;

        /// <summary>
        /// Used for CommonProvider external roles mapping.
        /// This is ordered list! First in == more important
        /// </summary>
        static Roles()
        {
            RoleList = new List<string>()
            {
                { Administrator },
                { RegisteredUser },
                { Viewer },
                { Translator },
                { Anonym },
            };
        }
    }
}

RoleProvider summary

Catharsis allows and may be forces you to use the separated access rights management. What ever could be done on one separate place, it must be done separately. You can than reuse it and adjust it without no impacts to other parts of your application. That’s the pure AOP.

Navigation

There is a built-in webControl in Catharsis TreeView. This webControl is 100% MVC and should be described more detailed. Maybe next time…

Different roles has different Navigation manu
This control is used on a master page and serves as the main navigation gate for the user. From its hierarchical nature it contains nodes with theirs nested content – controller’s links. Click and users are navigated to asked controller. There are Action’s links and Buttons, which allows other interaction (of course based on access-rights).
How to fill this Navigation TreeView webControl? Are there needed information for this TreeView already stored somewhere or do we have to create new source. Foolish we would be, if we won’t use the Menu.config once again…

Menu.config – smart source for navigation

Customer needs were, as described above, fairly transferred into the Menu.config access rights setting. This is the best and very smart source for our navigation control. We can easily reuse the known role-based-conditions and there for build up the navigation links menu.
The already mentioned NavigationMenuProvider will do this job. It will read the nodes and controllers and correctly supply the TreeView.
To have the TreeView well looking we can extend Menu.config settings with some other attributes. These have no impact on CurrentRole access rights management, but manage the TreeView look.

Config.xsd

To help you with filling these elements and attributes there is an XML Schema for you: Config.xsd. It has no validation impact (at least it’s not used for that), it only supports the InteliSense which is always nice to have. (I’m missing this for Log4Net, if you have any…)
Smart attributes for Menu.config
Snippets below reveal the full strength of this file setting.

XML
<controllerOrNode name='Home' expanded='true' >
    <roleGranted name='Administrator' isVisible='false' />
    ...
</controllerOrNode>

Attribute isVisible="false" helps you to hide some controllers from the navigation menu (without any impact on RoleAccess!!!)

XML
<controllerOrNode name='CodeLists' href='' 
        imagePath='/Content/img/currency.gif' expanded='true' >

Above example shows another attribute “href”. It can be filled with the specific Url, or by leaving it unused, the ActionLink based on controllername” will be created for you. The provided empty value in the example is a trick how to disable the link at all (nodes does not have targets). The second attribute imagePath provides the relative path to the image which can decorate your node

XML
<controllerOrNode name='Currency' imagePath='.' parent='CodeLists' >         
    <roleGranted name='RegisteredUser' />
    <roleGranted name='Viewer' isReadOnly='true' />
</controllerOrNode>

Parent attribute is essential for the correct parent-child relations. The imagePath is set to special sign “dot” imagePath="." this provides smart abbreviation for the engine to create the path using controllerOrNodename” attribute => “/Content/img/menu/ + name + .gif”

XML
<controllerOrNode name='Country' text='State' parent='CodeLists' >         
    <roleGranted name='RegisteredUser'  />
    <roleGranted name='Viewer' isReadOnly='true' isVisible='false' />
</controllerOrNode>

In the last snippet the different text will be rendered then the controllerOrNode name attribute.
Play with, and if not sufficient, adjust it.
TreeView overall look can be also adjusted in the NavigationWC.ascx control using TreeViewSettings:

XML
<pbwc:TreeView ID='NaviTV' runat='server' ControllerActionUrl='/Home.mvc/NaviTV'
 ViewDataKey='MasterModel.NaviTVModel' >
  <TreeViewSettings
    DoLocalize='true'
    ShowExpandCollapse='true'
    ImgExpandPath='/Content/img/menu/expand.gif'                                
    ImgCollapsePath='/Content/img/menu/collapse.gif'
    ShowAnchors='true'
    ShowImages='true'
    ImageNodeClosedPath='/Content/img/menu/closed.gif'
    ImageNodeOpenedPath='/Content/img/menu/opened.gif'
    ImageLeafPath='/Content/img/menu/leaf.gif'
  />
</pbwc:TreeView >
 

For example, the switch DoLocalize (if set to true) will call the ResourceProvider to get localized names from global resources. That's new feature of Catahrsis 0.9.8 - Localizer's are directly connected to ASP.NET ResourceProviders, but it's another story.

Summary   

The Catharsis framework, as we’ve seen above, provides very powerful solution for your application for the access rights management. Once correctly set - immediately reused for navigation menu. It is easy to adjust, possible to extend. And easy to learn, use and maintain! You never know how the Customer needs will look like tomorrow…

Enjoy Catharsis

Appendix

I cannot finish this chapter without saying thanks. Thank to MS for the Linq. Especially the Linq to XML - it is the tool which changed development for me in surprising way. Old school hard-coding with Xml readers, writers, serializers etc. could be fun. But the Linq to XML is the real Catharsis. Great. Thanks. 

Source code

Catharsis source code: http://www.codeplex.com/Catharsis/

History   

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Czech Republic Czech Republic
Web developer since 2002, .NET since 2005

Competition is as important as Cooperation.

___

Comments and Discussions

 
GeneralUsers, Roles, Permissions and Resources Pin
Eng. Jalal12-Oct-08 12:41
Eng. Jalal12-Oct-08 12:41 
GeneralRe: Users, Roles, Permissions and Resources Pin
Radim Köhler12-Oct-08 19:44
Radim Köhler12-Oct-08 19:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.