- Custom Authentication
- Hybrid Authentication Scheme
- Operation Based Security
- Conclusion
Operation Based Security
In custom developed applications, role based security provides a sufficient degree of flexibility. However, when the same application is running on different customers' sites, some issues are likely to arise. Suppose that you have a financial application. In this application, a class is responsible for triggering the payment procedure of financial deals. Using role based security you have defined a role hierarchy and placed a security demand for BackOffice or Administrators roles on the class that runs the payment procedure. Given this structure, a customer tells you that, in his company, people in the role of FrontOffice can run the payment procedure as well. Unfortunately, adding the FrontOffice employees in the BackOffice role as well is not an option because you would give them access to all the sensitive operations assigned by your application to the BackOffice role.
At this point, you are stuck in the sense that you need to develop a customization for that customer; that is, you need to recompile the previously mentioned class in order to have it require BackOffice or FrontOffice membership.
The solution to this problem is to ask for permission on specific operations in the application code instead of role membership. The definition of which roles can execute a specific operation is pulled out from the compiled code and placed into another configuration file. In the proposed implementation of operation based security, this file will look like the following:
<root> <operation name="sensitiveoperation"> <role name="ApplicationAdmins" /> </operation> <operation name="openform"> <params> <param name="formname" value="reports" /> <param name="edit" value="true" /> <role name="ApplicationAdmins" /> </params> <params> <param name="formname" value="reports" /> <param name="edit" value="false" /> <role name="ApplicationUsers"> /> <role name="ApplicationAdmins" /> </params> </operation> <operation name="noadmins"> <params> <param name="formname" value="anotherform" /> <role name="ApplicationUsers" /> </params> </operation> </root>
An explanation is in order for the param attributes. In a real world application, it's quite difficult when all access checks can be expressed declaratively against a single operation-name string value (as shown in the first operation section of the sample XML file). For instance, consider how to express the permissions for opening a form in read-only mode for all users and read-write mode for power users.
What you need is the opportunity to specify some additional parameters on each operation name. The number and meaning of name value pairs are unconstrained. The authorization module will accept any number of them from the calling application and will try to match them on the operation configuration file constructing the proper XPath query, totally oblivious of their application meaning. That said, it should be quite clear why role assignment in the configuration XML file isn't done at operation level, but at the parameters set level instead.
The complexity and the number of parameters required on average for an operation check may vary greatly, and it depends a lot on the encapsulation and the good design of the application in which you are applying authorization checks. A brittle application architecture will lead to a brittle structure of operations (and their parameters).
The Authorization module based on operations checks extends the one shown in the previous paragraph (you can find both of them with the downloadable package that comes with this article).
Now the MycustomPrincipal class implements a new interface named IOperationCheck, which exposes a single method named IsOperationAllowed.
public interface IOperationCheck { bool IsOperationAllowed(string operation, params string[] p_params); }
As you can see, this method accepts an operation name and an arbitrary number of parameters. Each one must be packed in the following form: "<paramname>=<paramvalue>". Following is a code snipped showing a client application calling the IsOperationAllowed method.
if(((IOperationCheck)Thread.CurrentPrincipal).IsOperationAllowed( "openform","formname=reports","edit=false")==true) //do work.. else throw new Exception ("Access Denied");
The implementation of the IsOperationAllowed is quite straightforward after you get the XPath query right. As you can see below, the construction of the XPath query for role membership and parameter matching has been factored into two separate private functions that are omitted for brevity (you can find the full code source here).
public bool IsOperationAllowed(string operation, params string[] p_params) { lock (i_xmloperations) { XmlNodeList l_nodelist = i_xmloperations.SelectNodes( "/root/operation[@name='" + operation + "'" + pf_buildRoleClause(p_params) + pf_buildParamClause(p_params) + "]"); return l_nodelist.Count > 0 ? true: false; } }
As a possible refinement, you could extend the params syntax to evaluate against inequality as well:
<operation name="noadmins"> <params> <!-- calling code must provide a value greater than 1000 for the income parameter --> <param name="income" value="1000" operator=">" /> <role name="ApplicationUsers"></role> </params> </operation>
No matter how you extend the XML grammar, even a really well-designed application might require authorization logic at some point that can't be expressed in the form of name-value pair matching. (For instance, how can you declaratively implement access checks such as "Application users can do this if A is greater than 100 or B is less than 1000 or it's from 4 pm to 6 pm"?)
The solution in this case is to define some mechanisms and the proper grammar in the XML file to hook dynamically bites of scripting-based authorization logic. Unfortunately, this is out of the current article scope, so it's left as an exercise for the reader <g>.
However, if you want to follow this path, here's some advice:
Try to see if refactoring at method or class level can eliminate the need for code-based access checks.
Do not mix unwary access control and business logic together (specifically, avoid treating business logic as access control) because this can brittle your application architecture.