Authorization using AZMAN
Authorization needs to be done in most of the ASP.NET applications. There are many ways to do authorization in an web application. The techniques involve using the Microsoft AzMan or doing custom authorization based upon a data store like database / xml. However you do authorization that depends upon your needs and resources available, but what I am going to describe is how to centralize the authorization task and identify a pattern for the same.
The pattern to centralize the authorization is again based upon the Page Controller pattern and is similar to the one that I described in my previous post of clearing session variables. I would be using a custom library to do actual authorization using the AzMan authorization provider that I would be describing later in the entry.
Now lets get back to the implementation:
Similar to what I have described in my previous entry we need to have a Base Page where we would be centralizing the call to Authorization Check.
1. Assuming that we have our infrastructure ready i.e. the Authorization store ready in form of xml or active directory and its confguration done already.
2. Declare the Task and Operations Enum under Web Application that should match the task and operations declared in authorization store
Code:
/// <summary>
/// Autorization Manager Tasks
/// </summary>
public enum Tasks
{
/// <summary>
/// default tasks set in base page
/// </summary>
NotDefined,
/// <summary>
/// Search Requests
/// </summary>
SearchRequests
}
/// <summary>
/// Authorization Manager Operations
/// </summary>
public enum Operations
{
/// <summary>
/// default operation set in base page
/// </summary>
NotDefined,
/// <summary>
/// Resource
/// </summary>
Resource
}
3. Declare Page Constants that refer to Tasks that would be part of the navigation menu of the Web Application
Code:
/// <summary>
/// Summary description for SitePageConstants
/// </summary>
#region SitePageConstants
public class SitePageConstants
{
.......
public const string SC_PAGE_RESOURCE = "Resource/Resource.aspx";
............
}
4. Declare a Method IntializeTasksOperations() that will be called in Application_Start and will store the details of Tasks and Operations with URL details in an Application object.
Code:
#region InitializeTasksOperations
/// <summary>
/// Initializes the tasks operations.
/// </summary>
public static void InitializeTasksOperations()
{
Dictionary<Web.UI.Enums.Tasks, Tasks> dictionaryTasks = new Dictionary<Web.UI.Enums.Tasks, Tasks>();
// Search Open Non Sensitive Requests
AddTasksToDictionary(ref dictionaryTasks, Web.UI.Enums.Tasks.SearchRequests, SitePageConstants.SC_PAGE_RESOURCE, 1);
HttpContext.Current.Application.Add(Constants.C_TASKS, dictionaryTasks);
}
/// <summary>
/// Add tasks to dictionary
/// </summary>
/// <param name="dictionaryTasks">Dictionary<Web.UI.Enums.Tasks, Tasks></param>
/// <param name="taskEnum">Web.UI.Enums.Tasks</param>
/// <param name="pageConstant">string</param>
/// <param name="displayOrder">int</param>
private static void AddTasksToDictionary(ref Dictionary<Web.UI.Enums.Tasks, Tasks> dictionaryTasks, Web.UI.Enums.Tasks taskEnum,string pageConstant, int displayOrder)
{
Tasks task = new Tasks();
task.Page = pageConstant;
task.DisplayOrder = displayOrder;
dictionaryTasks.Add(taskEnum, task);
}
#endregion
5. On Session_Start - use the identity of the current user and intialize the ServicePrincipal (refer custom library) by passing the identity of the user.
Code:
void Session_Start(object sender, EventArgs e)
{
.......
// Get the identity of the logged in user.
System.Security.Principal.IIdentity loggedInIdentity = System.Web.HttpContext.Current.User.Identity;
ServicesPrincipal principal = Security.AuthorizationManager.CreatePrincipal(loggedInIdentity); // refer custom library section
........
}
6. Store the ServicePricipal created above in a Session variable
Code:
void Session_Start(object sender, EventArgs e)
{
..........
Session.Add(SessionConstants.PRINCIPAL, principal);
..........
}
7. Declare a Base Page which has a private variable that controls the execution of whether we need to apply authorization on the Derived page method, but default it to true.
Code:
/// <summary>
/// The Base page that needs to be inherited by a all the web pages and contain
/// all the common tasks that needs to be performed.
/// </summary>
public class MyBase : System.Web.UI.Page
{
..............
.........
/// <summary>
/// Initializes a new instance of the <see cref="MyBase"/> class.
/// </summary>
public MyBase(bool applyAuthorization)
: base()
{
_applyAuthorization = applyAuthorization;
}
..............
.........
/// <summary>
/// control the call to authorization function
/// </summary>
private bool _applyAuthorization = true;
}
8. Declare two private variables in the Base Page which defines will be holding the Task / Operation performed by the derived page and must be of type ENUMs - Tasks / Operations defined in step 2. And even define the get / set properties for the same.
Code:
public class MyBase : System.Web.UI.Page
{
..............
.........
/// <summary>
/// Gets or sets the page task.
/// </summary>
/// <value>The page task.</value>
public Web.UI.Enums.Tasks Task
{
get { return _task; }
set
{
if (value != Web.UI.Enums.Tasks.NotDefined)
{
_operation = Web.UI.Enums.Operations.NotDefined;
}
_task = value;
}
}
/// <summary>
/// Gets or sets the page operation.
/// </summary>
/// <value>The page operation.</value>
public Web.UI.Enums.Operations Operation
{
get { return _operation; }
set
{
if (value != Web.UI.Enums.Operations.NotDefined)
{
_task = Web.UI.Enums.Tasks.NotDefined;
}
_operation = value;
}
}
/// <summary>
/// the page task needs to be set in the Pre Init event of the page
/// that is derived from the base page and if its default pageOperation
/// will be examined and if that is also not set then an authorization
/// exception will be thrown
/// </summary>
private Web.UI.Enums.Tasks _task
= Web.UI.Enums.Tasks.NotDefined;
/// <summary>
/// the page task needs to be set in the Pre Init event of the page
/// that is derived from the base page and if its default pageOperation
/// will be examined and if that is also not set then an authorization
/// exception will be thrown
/// </summary>
private Web.UI.Enums.Operations _operation
= Web.UI.Enums.Operations.NotDefined;
}
9. Define a method in the Helper that do the actual authorization based upon the values of the either operation / task set or passed:
Code:
#region Authorization
/// <summary>
/// Authorizes the specified apollo principal.
/// </summary>
/// <param name="ServicePrincipal">The service principal.</param>
/// <param name="pageTasks">The page tasks.</param>
/// <returns></returns>
public static bool Authorize(Security.ServicesPrincipal principal, Web.UI.Enums.Tasks pageTasks)
{
return principal.HasPermission(pageTasks.ToString());
}
/// <summary>
/// Authorizes the specified apollo principal.
/// </summary>
/// <param name="apolloPrincipal">The service principal.</param>
/// <param name="pageOperation">The page operation.</param>
/// <returns></returns>
public static bool Authorize(Security.ServicesPrincipal principal, Web.UI.Enums.Operations pageOperation)
{
return principal.HasPermission(pageOperation.ToString());
}
/// <summary>
/// Authorizes the specified apollo principal.
/// </summary>
/// <param name="ServicePrincipal">The service principal.</param>
/// <param name="pageTasks">The page tasks.</param>
/// <param name="pageOperation">The page operation.</param>
/// <returns></returns>
public static bool Authorize(Security.ServicesPrincipal principal, Web.UI.Enums.Tasks pageTasks, Web.UI.Enums.Operations pageOperation)
{
bool returnValue = false;
if ((returnValue = Helper.Authorize(principal, pageTasks)) == false)
{
returnValue = Helper.Authorize(principal, pageOperation);
}
return returnValue;
}
#endregion
10. Now on the base page deifine a private method Authorize that calls the Helper Authorize() method with correct parameters:
Code:
#region Authorization
private void Authorize()
{
ServicesPrincipal principal = Session[SessionConstants.PRINCIPAL] as ServicesPrincipal;
if (!Helper.Authorize(principal, _task, _operation))
{
throw new ExceptionMgmt.BaseException("AuthorizationFailure"));
}
}
#endregion
11. Now override the OnLoad event on the base page and based upon the value of the variable that controls the authorization make a call to private Authorize method.
Code:
/// <summary>
/// Raises the <see cref="E:System.Web.UI.Control.Load"></see> event.
/// </summary>
/// <param name="e">The <see cref="T:System.EventArgs"></see> object that contains the event data.</param>
protected override void OnLoad(EventArgs e)
{
if (_applyAuthorization)
{
this.Authorize();
}
base.OnLoad(e);
}
12. Inherit all the pages from the Base Page, override the OnPreInit() on the Derived Page and set the base page exposed Tasks or Operations property based on the business needs.
Code:
public partial class DerivedPage : MyBase
{
/// <summary>
/// Raises the <see cref="E:PreInit"/> event.
/// </summary>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
#region OnPreInit
protected override void OnPreInit(EventArgs e)
{
this.Task = Web.UI.Enums.Tasks.SearchRequests ;
base.OnPreInit(e);
}
#endregion
}
I am attaching a library that can be used directly to do Authorization using AzMan. The assembly have to placed in the bin folder and the configuration in web.config is as follows:
<configSections>
<sectionGroup name="AZMAN_POLICYSTORE">
<section name="AZMAN_APPLICATION" type="Security.CustomAzManConfiguration, AzMan.Security" allowLocation="true" allowDefinition="Everywhere"/>
</sectionGroup>
</configSections>
<AZMAN_POLICYSTORE>
<!-- Active Diectory Data Store -->
<!--
<AZMAN_APPLICATION policyStore="machine:65530/CN=AzManADAMDataStore,OU=Services,O=ABC,C=US" application="MyApplication" providerStoreType="ActiveDirectory" />
-->
<!-- Xml Data Store -->
<AZMAN_APPLICATION policyStore="D:\authorization.xml" application="MyApplication" providerStoreType="Xml"/>
</AZMAN_POLICYSTORE>
Please share other approaches and discuss the pros and cons.
Comments
- Anonymous
February 19, 2007
Considering AzMan is "not-managed" code, what do you think of performance of using it for heavy traffic web app? :)We use it a bit differently at my end and it works very well for an intranet app but just wondering how good it would be for internet scenerio! - Anonymous
February 20, 2007
I have used AzMan extensively in a large .NET Web application. It needs to be architected correctly architect based upon application for performance.