Coding, is? Fun!

Saturday, April 03, 2010

The Two-Hop issue and Web apps

Consider these two scenarios:
1. Your ASP.NET web app has an <identity impersonate="true"/> in the web.config. You are accessing SQL server from your web app using Windows authentication. Your users' windows accounts DO have access provided in SQL server. IIS is set to Integrated Windows authentication.
Your application works locally or in a Dev server as long as SQL server is installed in the web server itself. You move the app to User Acceptance environment, where SQL server is in a different box from the web server. Permissions are unchanged. Yet, your web application throws an authentication error when accessing SQL server - it is a classic case of "it works in dev, but not in other environments".

2. Your ASP.NET web app accesses a network file share for some files. The web app, again, has <identity impersonate="true/> in the web.config. Your users have permission to the network file share. IIS is set to Integrated Windows authentication.
Your app works fine in dev, as long as your browser is in the same box as the web server. Thus it works well in development. You move the app to a different server, SRV 1. All of a sudden, even for you, when you browse the app from your computer and hit SRV 1, you get an authentication error - suddenly, your network share is not accessible.
But it works if you open the browser in the SRV 1 box itself!

When encountering the above two and various similar scenarios, I have seen developers think that the problem is specific to SQL server, their web app, IIS or the network share. But what you are probably facing is the dreaded Two-Hop issue - it is an important infrastructural issue. It has nothing to do with specific services like SQL Server or IIS.

The Two-Hop issue
Very specifically, what you are attempting in the above two scenarios is this: you are trying to traverse, using an user's windows identity, from your browser to the web server (First Hop) and then from the web server to the SQL server (or network share or LDAP server - this is the Second Hop).
The user (you) have entered your windows network user name and password in your box, and you have been authenticated. With this authentication (that is, with your password) you can ONLY go from your box to one other (any other) server that you have permission to. Once your id reaches the web server, the web app is trying to pass it on to the SQL server or network server(that is the meaning of <identity impersonate="true"/>). BUT IT CANNOT DO SO! Why not? Because in a windows network, the primary authentication token (in other words, your password) is only valid for one server to another. The recipient server cannot pass it on to another server and so forth.
This is a very important security restriction - and it has nothing to do with specific services like IIS or SQL server. It is the nature of the windows security infrastructure.
When IIS or SQL server "authenticate" you, and they use Integrated Windows Authentication, then the authentication process is handled by the underlying operating system. The operating system treats it simply as a request from one server to another. The web app's request to SQL server is no different from opening a network share on the SQL server machine from the web server - both these requests use the SAME authentication mechanism.
Since the flow is from server to server, the Two-Hop issue kicks in. Now let us see the above scenarios again:
Your browser makes the request to web app - you enter your network user id and password. Therefore you are authenticated to the web server.
ASP.NET now sees the <identity impersonate="true"/> tag and uses your identity for all further network requests.
Now, the web app makes a connection to SQL server.
If the SQL server is on the same server (as the web app), then you are already authenticated to the web server (it is the first hop). Therefore, SQL server successfully authenticates you.
Instead, as is normal, SQL server is on a different server, then the web app tries to pass on your identity to the SQL server service. The service now asks the underlying operating system to authenticate your network id. That authentication FAILS because the call from the web server to the SQL server is a second hop.

The same applies to the network share scenario.

Implications
What does this mean for enterprises? Enterprises prefer Windows authentication in SQL server because it is not passed around easily and is much easier to manage. But SQL server with windows authentication will NEVER work the ideal way for web apps - by passing through the user's identity from the browser to the web server and then the web server to a separate SQL server.
The enterprise faces the following options:
1. Go for SQL server authentication and its attendent issues (such as passwords and user ids being shared by everyone).
2. Go for Windows authentication, BUT impersonate a specific "service" user account in the web app. That is, instead of setting "<identity impersonate="true"/>", set "<identity impersonate="true" username="appserviceuser" password="password"/>".
You could also encrypt this password and thereby protect yourself from attacks.
In SQL server, then, you give this serviceuser account limited privileges.
This will work because now you are not passing in the primary (browser) user's token. Instead you are generating a new primary token (through the password) for a specific account and authenticating with SQL server.(This will also work for network shares).
The big con here is the proliferation of such service user accounts, making them compliant with password reset policies etc. For example if the service user account's password expires after 30 days, suddenly one fine morning your web app will stop working. So you have to go in and change the password every time.
But, this is the option many smaller companies choose.

3. Use Basic Authentication in IIS. This means that your password is available in the web server and the web application can then (programmatically) use an API to impersonate the web browser user.
This is a really bad option, for a simple reason - let us say this particular app is used by the company's executive management. The web developer can log that supplied password to a file or email it to himself and then get sensitive information about the company.

4. Enable Kerberos delegation
This is a complex mechanism - but the crux is that setting a certain set of options in Active directory enables the web server (or any second server in the first hop) to propagate the server's primary token to other downstream servers. This is a preferred option in some places but it does have a downside: similar to basic authentication, the web developer can use the higher privileges of a end user and programmatically do things he/she is not supposed to do -such as access the boss's email account.

5. Use SiteMinder or Micorosft ISA or such a Single Sign On solution. This will usually result in an Agent mediating authentication. Using such a solution, you can basically use Windows authentication all through - for example, we recently implemented a Business Intelligence solution, with SiteMinder mediating. We could propagate from Sharepoint (MOSS 2007) in one web server to Reporting Services in another to Analysis Services in yet another server - no issues. But this is an expensive solution that bigger enterprises use.

So, take the decision to use Windows authentication (for web-browser end users) in SQL server carefully - it may not be a simple choice.

Summary
The simplest way to remember the Two-Hop rule is this: when you supply a password, it is good for one server to another. That is it. For propagating your identity further in a windows network, you need to enable delegation or collect the password and do it programmatically.
It is important to note that this is not a "limitation" in the network - it is a good rule and exists for a reason.

Labels: , , ,

Thursday, February 25, 2010

Microsoft Project and Project Plan Creation

I have seen a few project managers and most technical people have dificulty creating basic project plans that refect reality and are trackable. I learnt some details about creating simple project plans that I explain here.

1. As a first step, enter the project start date in the project information dialog.
2. Enter holidays and work timings in the appropriate dilogs.
3. Enter a resource list and their availability (50% etc)
4. Add a column called "Work" to the column list. Work is the actual timing for completing a task - it is different from the duration (which is a default column). If a task take 16 hours and two resources work on it, then the Work is 16 hours and the Duration will be a single day of 8 hours.
Always ente work and never enter duration. MS Project will manage duration itself.
5. Start entering the tasks, the Work and the Resources for a task. Do NOT enter dates (start date and end date) when you enter tasks. Dates will be automatically calculated when you level at the end.
6. Tasks can be nested. More on task ordering later.
7. Once you have finished step 5, enter predecessors for the tasks. There are some rules here. NEVER enter predecessor task numbers just to adjust dates. If your dates are wrong, that means your project task arrangement is wrong.
If three tasks under a task heading are done by the same resource(s), then do NOT enter predecessors for each of the three taks. Leveling will take care of the dates correctly.
Again, I repeat, only enter predecessor information if a task actually, in real life, depends on the other task being complete, and is done by a different resource.
8. Now, click on the Level button in the Manage Resources dialog. You HAVE to level a project plan. Many people are afraid of leveling because it seems to mess up the dates - but leveling never goes wrong. What must be wrong is the way you arranged your project tasks or predecessors.
9. If, after levelling, your dates seem wrong (such as different tasks for the same resource starting the same date), do not turn off leveling. Make sure the predecessors are correct. Avoid the temptation to manually enter dates in the mpp.

Project Task Arrangement
In one of my recent projects, we had a Extraction, Tranformation and Loading (ETL) project plan for a business intelligence project. Data is moved from a Temp database to a Staging database and then to a Final database. We were moving data from 20 different files.
The initial project plan simply bunched together the Temp database tasks under one task heading and the Staging database tasks under another. This made the project task predecessors difficult to track.
We had this problem because we saw the project plan simply as a task list instead of reflecting reality. Such a project plan will be useless to track and will quickly grow out of sync with the "real" project status.
After mucking around, we changed it so that each of the 20 files had a separate task heading, with three tasks: Temp data transfer, Staging data transfer and Final data transfer. This led to easier tracking and capturing the natural dependencies between the tasks.
So, be creative in project planning. Do not just dump in your tasks. A big advantage in proper project planning is the ability to do projections - in case you add resources or tasks take more time. For doing projections, it is important that you have a good baseline projectplan.

Labels: ,

Saturday, November 21, 2009

Exception Handling in .NET projects

(Updated below)
Here are a few gudelines for Exception Handling. I learnt some of this from the book ".NET Framework Guidelines".

Here are a few questions: Whenever you write a method in a class, is it a good practice to wrap the code in a Try..Catch..Finally block? For example, let us say you are writing a function, GetProductsByProductId. Should thismethod have a Try...Catch..Finally block?
The answer is No. It should have a Try..Finally block, but the Catch is not necessary for every method.
Do NOT catch an exception unless you have a specific reason for handling it. Put a global exception handler and let such exceptions bubble up and be caught in that.
As a corollary, do NOT ever catch the general exception. That is, do not do this:

try
{
...
...
}
catch(Exception ex)
{
...
}


Exception handling needs some thought to go into it. Have a Catch block only if you have a specific reason for handling a specific exception.
One example is this: let us say you load configuration from a file. If the file is not available, you assume certain defaults. In that case, you can handle the "FileNotFound" exception and take an alternative route.

Throwing exceptions
Generally, do NOT catch an exception, wrap it in a custom exception and rethrow it. Let us say a method is not getting the correct parameters. Then, use the .NET exception "InvalidArgumentException". Do not write a custom exception and throw it.

Custom Exceptions
Custom Exceptions are exceptions in your application code, derived from the base Exception class. They can be created for a few purposes. For example, let us say you are handling concurrency checks in the db layer. In case of a failure, you can throw a custom exception. What is the purpose of a custom exception? Usually an application framework handles unforeseen circumstances through a custom exception so that program flow on higher layers can perform useful actions based on such exceptions. Such exceptions are NOT a substitute for well designed interfaces that return correct values.

Service Layer Exceptions
As a best practice, any service methods should ideally return well designed Error objects instead of relying on Custom Exceptions. Web service methods are system boundaries and should not rely on exception handling for propagating errors.
So, handle general exceptions and log them at the service entry. Then return specific error objects from all methods - such as ones with an error code, message (or message id for localizations) and error type.

TryGet Methods
There are a set of methods such as int.TryParse which simply handle failure through a return boolean value. These kind of methods are conventionally written starting with Try.. such as TryGetConfigValue. These methods need not suppress ALL exceptions. They handle an exception and return an error value only if the primary purpose fails. If you have a TryGetConfigValue method and there is an exception getting the config value, that should be suppressed. But such methods need not suppress all exceptions. Any exception unrelated to their primary purpose CAN propagate.

Update I:
My friend Mr.Adrian Gonzalez suggested a few changes to the above post. I am adding them as edits here:

1. I mentioned above that a try..finally block may be necessary in many methods. The purpose of the "finally" is, of course, so that any cleanup code can reside there. Remember that the finally code is ALWAYS executed.
This used to be the place where developers would close database connections or close filestreams. But, .NET has a much cleaner way of closing costly resources: any object that implements the IDisposable interface can be cleaned up the following way:

using (SqlConnection ObjCon = new SqlConnection(Helpers.GetConnectionString()))
{
//do something
}//connection object is closed and recovered here

An instance of any class that implements IDisposable will support the above syntax. Much cleaner than closing the connection after checking its state in a finally block.
Further, do not throw an explicit exception from a finally block.

2. One of the few places that it is ok to capture a general exception (catch(Exception ex)) is when you want to log an exception at that point. For example, developers do this at a service method's root. That is fine.
When you RETHROW an exception, this is good:

try
{
...
}
catch(Exception ex)
{
throw;//preserves call stack for logging
}

This is bad:

try
{
...
}
catch(Exception ex)
{
throw ex;//does not preserve the call stack for logging
}


3. Always include and copy the inner exception when creating a custom exception. Always log the inner exception, if one is available.

4. I mentioned service methods above. In a WCF based web service, there is a way to include an exception as part of your contract. This is called a FaultContract. This has two benefits - you allow specific exceptions to bubble up to the client, in a strongly typed fashion. The client knows to handle them appropriately, even though they are on the other side of a serialization boundary.
The second benefit is that you can add some specific details about the exception back to the client.
For more details about faultcontracts, refer here: Fault Contract Sample

Labels: ,

Sunday, September 27, 2009

Radius Search, Http Screen Scraping and String to Temp Table

In the last few days I have been assisting on a friend's website. I have here some code snippets and information I learned.

Comma Separated Id List to Temp Table
Some times you have to do a search on a set of values. Let us say the user is searching through a list of zipcodes for someone close to them. You can write dynamic sql to pass in the list of zipcodes. But then you have to grapple with table security and it will not be fast enough.
Instead, the standard method is to do this:

1. Pass in list of zipcodes as a comma separated varchar string.
2. Create a temp table with a zipcode column
3. Parse the passed in string so that it populates the temp table.
4. Now, join the temp table with the core tables to execute your query.

I have below a code snippet for this:


ALTER PROCEDURE [dbo].[Test_Sproc]
@IdString varchar(1000),
@numIds int
AS

CREATE TABLE #zip (zipcode varchar(5) NOT NULL);

DECLARE @count int, @index int, @start int, @id varchar(5), @length int, @previndex int
SELECT @count = 0, @start = 0, @index = 0, @length =0, @previndex =0
WHILE @count < @numIds
BEGIN
SET @index = CHARINDEX(',',@IdString, @index)

IF @index = 0
BEGIN
SET @length = LEN(@IdString)- @previndex+1
END
ELSE
BEGIN
SET @length = @index - @previndex
END
SET @id = SUBSTRING(@IdString,@start, @length)
INSERT INTO #zip (zipcode) VALUES (@id)
SET @count = @count + 1
SET @start = @index + 1
SET @index = @index + 1
SET @previndex = @index

END

SELECT u.UserId, u.UserName
FROM [User] u
WHERE u.ZipCode IN
(SELECT zipcode FROM #zip)

DROP TABLE #zip



Radius Search
I found that there is a requirement to do zipcode based searches in a geographic circle. Let us say you want to find some restaurants in your neighborhood. Your application lets you search restaurants in a widening circle - such as within a circle of 5 miles, 10 miles and so on.
For this purpose, you need a database of restaurants with address and zipcode. But you also need a zipcode database. The only way you can do a radius search is by getting the lattitude and longitude information.
So you need two components to perform a radius search:
1. A zipcode database that gives you lattitude and longitude information also. Such databases are available commercially for around $100.
2. You need a component that can take the lattitude and longitude and figure out the radius. Such components in Java and .NET are available commercially. You can also build them pretty easily.
Using these you can build a radius search for your users.

Parsing a webpage
In a hurry, I also needed to parse a certain web page for some information, such as a date. If I got the webpage contents in a string I could use regular expressions to parse it. Here is a simple code for getting the contents of a webpage, in C#:


string str = String.Empty;
string url = "http://www.yahoo.com";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
{// throw exception
throw new Exception("Could not get response from web pageUrl");
}
// Read Content
Encoding enc = Encoding.GetEncoding(1252);
using (StreamReader responseStream = new StreamReader(response.GetResponseStream(), enc))
{
str = responseStream.ReadToEnd();
}
}



At the end of the this code, str contains the entire front page contents of yahoo.com. You can then use Regular Expressions to parse it. The above code requires the namespaces System.Net and System.IO.

Labels:

Wednesday, August 26, 2009

A New Model used in Rich Internet Applications

For the past few months I have been working with an Ajax based Rich Internet Application. The architecture in this app is very different from the architecture used in traditional postback-based ASP.NET or JSP applications. This article explains that architecture.

Traditional n-tier Architecture
In traditional layered architectures, the focus is on creating a reusable middle tier in a .NET, Java, PHP or RoR platform. There are a couple of patterns to do so:
1. Create a database layer that accurately represents your business model. Then use an "Active Record" pattern to represent table rows with objects in the middle tier. This means, typically, that there is a class per table. Foreign key and primary key relationships are captured as associations in the middle tier. Each column in a table typically has a property in the middle tier class.
When you use Active Record, your middle tier is a close representation of your database structure.
2. The other option is to create a Domain Model as a representation of your business model. Then map from the domain model to the database using Object Relational Mappers.
In both these options, the use of the middle tier for reusable business logic is a given. Enetreprises usually stay away from business logic in the front end, because enterprises do not usually trust Javascript for expressing business logic. A few reasons for this are: the lack of strong typing, difficulty in automated unit testing, lack of intellisense and strong editor support.

Rich Internet Applications (RIA)
Many applications use Ajax. Usage of Ajax does not classify an app as a Rich Internet Application.
RIAs are typically "single-page" apps - Applications that load a single page with Javascript content and then do not show postbacks (browser refreshes).
Think about a Flash based business application. The Flash movie clip loads additional components on demand. Any communication is using XML and is perfromed with web services in the middle tier.
RIAs, somehow, feel like the "correct" way of developing applications on the web. The client (browser) and the server have a clean separation based on data contracts. Development teams can reach the holy grail of working independently without stepping on each other's code.
Flash and Flex provide a way to deliver business applications with a good user experience. But they are proprietary and there are enterprises that shy away from them because finding developers who code Flex used to be difficult.

Instead enterprises go for HTML/Javascript based RIAs. These have a set of unique challenges:
1. Browser compatibilty - you need a library like JQuery or Prototype to code uniformly across browsers.
2. Component based UI - it is difficult to create UI components in Javascript. Several libraries provide pre-packaged components for grids and trees. But creating your own custom component is still a challenge.
3. Loading additional content and separating dialogs - UI frameworks handle this using iframes. But there is no standard way of handling dialogs. You need to come up with your own framework for that purpose.
4. MVC implementation - the client requires an implementation of the Model-View-Controller pattern so that the server calls and client side data are not tightly coupled with the UI. You may require a custom event pump for handling user actions and messaging the controllers.
Implementing MVC in the client leads to easily maintainable and reusable code. The Flex world have an accepted model called the Cairngorm framework. I am working with a Javascript adapted implementation in my project.

The big question in this case, is the location of business logic.

The Middle-Tier Goes Away
If you end up creating a business model in Javascript and communicate via services with the middle tier, you now have three different places to model the business - the database, the middle tier and in the browser client.
Instead, in my current project, we use the middle tier as a gateway. It simply delegates to stored procedures on the database end. The "data-specific" logic resides
in the sprocs. The UI logic is in the client. there is nothing in the middle tier.
I have heard of a few other businesses using this kind of model.
The biggest relief in this case is that we go from custom XML trees to relational data directly. There is no O/R mapping. The sprocs use XQuery to translate from XML to the relational tables. The transactions are all in the database.
What we have ended up doing is remove the Domain Model from the equation.

Working in this model has raised some deep questions. There are reasons the Middle Tier is the preferred stage (physically) for a business model:
1. It is close to the database; there is less cost to round trips.
2. It provides an object oriented, richer language for expressing business relations.
3. It provides encapsulation much better than available in the database layer.

But creating a full-fledged domain model in our case is redundant because we have a rich Javascript model to handle most of the logic.

From the n-tier model, this new architecture takes us towards a heavy client and a heavy database with a thin middle tier sandwiched in between.

Labels: ,

Friday, July 10, 2009

A concept in Facebook security

I have worked with a couple of Facebook apps since the platform was introduced. When working with developers, I have noticed that an explanation of a simple facebook security fundamental is missing. That fundamental concept helps in understanding what is possible and what is not possible very well. You do not have to check the API every time to understand it.
You may have uploaded files to a webserver. Have you noticed that there always needs to be a file upload button in HTML? You cannot automatically upload a file using Javascript. The reason is obvious - certain actions require user permissions in the browser. Unless the user explicitly clicks a button you cannot take some actions.

The Facebook security model has a simple concept like this:

The User Context
Let us take the case of getting a list of the user's friends. This is private information for the user. Therefore, the API call that gets a list of the user's friends needs the user's action to execute.
That is, you CANNOT get a user's friends unless the user is online in Facebook and explicitly clicked a button. If, on the other hand you DO want an app to perform this functionality, of getting a user's friends WITHOUT the user being online (say in the case of a batch job), you need to ask the user for an OFFLINE permission. This permission is not granted by default - it is an extended permission.
Facebook API calls are actually callbacks. When user clicks a button, Facebook sends the request to the third party app's website. That app then calls the facebook API back to get some information. Logically, even though an app is in a different web server, we can view the action from clicking a button, to the application's response as a single thread. This thread has the user's context (in the form of a TEMPORARY session id).
Thus, if you looked at the API call for getting an user's friends it does NOT usually take a input user id. It takes the current session key.
In the absence of a current session key, you can pass in a input user id, but the app needs to have the extended permission.
This applies to most API calls and it is a very easy concept to understand. In effect, Facebook is restricting you from using the Facebook user id as if it is a general database query identifier.

Scenarios

Let us take an example scenario your client comes to you with: User A visits the application canvas page and schedules a message for all his/her friends. That message should be sent only at midnight of User A's birthday. So the client wants you to store the user's request and then execute a batch job at the scheduled night to send the message to all friends. Can you do this?
The answer is you can't - without extended permissions. When your batch job executes at the middle of the night, it needs to get a list of User A's friends. For that it will call the Freinds.get API above. That API call WILL fail - because you are NOT getting the user's friends in the user's context. The user is neither online nor did they actually execute an action in the facebook app page. So, without the user's context, your call will fail.
Faced with this situation, I have seen developers suggesting workarounds - like storing the user's temporary session key in the database. My advice is GIVE UP! That restriction is there for protecting the users and you should not violate it. If you try to find a loophole, you will keep searching.

Let us consider another scenario - user enters a comment about a movie in the Flixster app. It goes to a moderator, who checks if the comment is appropriate. Then the moderator clicks the approved button - the client now wants the user's comments to be posted in the user's feed. Is this possible?
Again, the answer is no (unless the app has extended permissions). The moderator is NOT the original user. Think about it - would you like it if another user in facebook started posting entries in YOUR feed? You wouldn't. Therefore facebook does not allow (by default) an app to make entries in the feed outside the user's context.
Remember, this is different from the app itself asking you if it can post an entry in your feed. That will be allowed - because the app is still ACTING in the user's context.
Do you see the difference between the above two scenarios?
Let me explain again - let us say you take a Quiz. At the end of the quiz, the app prompts you if it can make the results available in your feed. This is fine, there is no violation of security. That is because the app is still having your session key available. You are online and you click a button. Facebook now allows the app to post an entry in your feed. The reason is because this is NO different from you posting it yourself. There is no extended permission needed for this case beyond the default permissions granted to an app.
What Facebook will NOT allow (without special permissions) is the ability of an app to post an entry to your feed OUTSIDE your user context - such as from a moderator's session.

In other words, Facebook allows apps to perform actions that the user can do when he/she is online. It does not allow apps to perform such actions on an user's network when the user is not online or from another user's session.

So, all that you have to do when a client comes up with a requirementis to ask yourself whether the action is within the user's context. If not, check extended permissions. Ideally an app should not have to operate with extended permissions. That is bad design.
Check out about Facebook's extended permissions here. You will see, for example, that an application can send Notifications when user is offline, but it CANNOT send Emails to user's inbox when user is offline (such as, through a batch process).

More information on Facebook Authorization is here.

Labels: ,

Saturday, July 04, 2009

Roles and Permissions - Authorization models

This post tries to capture the two different models in place for authorization in web applications.
Usually every web app has at least two different roles of users accessing the application. If you take a business application, like a timesheet managemenet system (my favorite example), you have employees who access the system to enter their time and task details. You have supervisors who access the system to approve such timesheets, and also enter their own task details.
The problem of identifying who the user is (by means of passwords or certificates) is the domain of Authentication. But, once the user is logged in, we have to allow or deny the user their various actions - this is the domain of Authorization. Authorization efforts are usually distributed all around the application code.
In the above timesheet example, an employee who is NOT a supervisor should not be shown task details of other users. This is accomplished in code by not showing the list of other users to a non-supervisor.
So, at some point in the code we have to make a decision of showing a link or not based on the user's authority.
If the application has to be really secure, the authorization will extend horizontally and vertically.
Horizontally, in another area of the code, let us say that the supervisor sees the timesheet of a person reporting to him/her. The "Approve" button for the timesheet is in that screen. The code that displays this button should still check if the user has the authority to approve or not. That is, even though a non-supervisor will NEVER gain entry to view another user's timesheet, the code for approval should still assume that they can and disable the approve button at that point.
Vertically, when the operation to approve a timesheet is actually called, the code should again check for the current user's authority to approve timesheets. That is, disabling buttons and links on the UI alone is NOT sufficient for authorization.

Thus, we can see that the code has to check for authority at different points in a given user's context. There are two different models for managing such authorizations.

Role and Permission based Authorization

For simplicity, let us consider a case of disabling or enabling the "Approve" button for an user. Remember the example extends both horizontally and vertically.
Authorizing a user based on their role assumes the following narrative:
A user who is in a supervisor role is accessing the Approval screen. Should the "Approve" button be enabled or disabled?

Authorizing a user based on Permissions assumes the following narrative:
A user who has the Approve permission for the Timesheet object is accessing the Approval screen. Should the "Approve" button be disabled or enabled?

Readers familiar with .NET would see that the first method corresponds to the Principal.IsInRole() method in the IPrincipal interface. There is no equivalent for permission based authorization in .NET.
One of the most important challenges in implementing these models is that they should be configurable or data-driven. That is, an user's roles should be available in the database. Similarly user permissions should be available in the database, in the case of permission-based authorization.
Let us consider these models in more detail below.

Role-based Authorization

The Users of a non-trivial web-based system will be available in the database. During initial design, the roles throughout the system will be clear - in the Timesheet system above, there are supervisors, non-supervisors and system admins. System admins are a hidden role in most systems.
So, we need a Role table to store the system-wide roles along with their identifiers. The Role table is usually small and only has a few rows.
Next we need a table mapping Users to Roles. This is usually a link table, say, User_Role_lnk.
Note that users in this model have multiple roles. This is usual in most systems - Roles are not mutually exclusive. In the Timesheet example, a supervisor also needs to access regular features like entering timesheets for him/herself.
Thus, typically a User has multiple Roles.
When the user has been authenticated, the system loads their roles in some object and makes it available in the current thread's context. So at different points in the code, developers can check for role authority this way:
if(currentUser.IsInRole("supervisor"))
{
enable approve button
}
else
{
disable approve button
}


This is a simple model, but it can get complicated some times. Since the user can belong to multiple roles, for some code, you may need to do this:
if(currentUser.IsInRole("supervisor") or currentUser.IsInRole("admin")
{
do something
}

In a system with many overlapping roles, you could easily have many such if..else conditions, thereby creating code that is difficult to understand or maintain.

Permission-based Authorization

The argument for authorization based on permissions is as follows:
At every point when you need to check authority, we are concentrating on what role an user belongs to INSTEAD of concentrating on what the code itself needs permissions for. If we flip the foreground and the background, then we should focus on what permissions a block of code needs.
The ideal, in the case of Permission-based authorization, is code that looks like the following:
if(user.hasPermission("Approve") on object Timesheet)
{
enable Approve button
}

This generally creates more maintainable code. But the details of the data model can be complicated.
Remember that we need more entities to define permissions. Let us look at them, in light of the pseudocode above.
We need a User entity as before.
We need a list of Permissions
We need a list of Objects
Note that the goal is to be data-driven. Usually we call the objects that we are "protecting" (such as the Timesheet object in the above code) as a Resource. In one implementation I saw, the Resource name along with its fully qualified namespace was used to represent the protected Resource in a "Resources" table.
So, unlike the Role-based model, we actually need a list of Resources in the code that we are protecting.
At first, this seems difficult to understand. In case of disabling the "Approve" button, what is our Resource name? What Resource in the database should it translate to?
The "Approve" button protects the Approve operation (which is probably setting a flag) on the Timesheet object, which is an instance of the Timesheet class. So, the Resource we are "protecting" or "seeking authority for" is the fully qualified name of the Timesheet class.

So till now, the table model consists of Resources, Permissions and Users.

Roles in a Permission-based system

But when we are actually assigning permissions, we still would like to assign it to a group of users than a single user. The User inherits those permissions simply because they are a member of a group.
Sounds like a Role, doesn't it?
So, the permission based model needs to include the idea of roles for ADMINISTRATION, but when the Permissions are being evaluated at Runtime, the roles work only in the background. The developer still checks only for permissions. The roles are usually not exposed in the Security API.
Let me explain with an example:
We have a user A who is associated with two groups, G1 and G2. G1 has the Approve permission for the Timesheet object. G2 has the Create permission for the Timesheet object. Therefore, user A gets the Create AND Approve permissions at runtime.
This can of course quickly lead to another scenario - if G1 has Approve permission for the Timesheet object and G2 explicitly has the Deny permission for the Timesheet object, what does the user get?
This problem is not unlike the problem faced by an operating system when you login to Windows in an Active Directory network. Of all the conflicting permissions from different groups you belong to, which one wins?
This is a detail of implementation - it is upto the business analysts to determine if Deny trumps all permissions or not. You just have to be aware of this possibility when implementing a permission based system.


Implementation of a Permission-based Authority

As a summary, a permission based system requires a custom implementation. It requires you to take into account group conflicts. It also requires you to enumerate (in the database) Resources that you are protecting.
Building a GUI administration system for Permissions is complex, compared with simply populating User-Role link tables.
But this model is very common in business applications and produces more maintainable code.

Labels: ,