From Zero to launch - Part I
5 05 2008This is the first post in a series of entries that I will use to describe the architecture used in my most recent social networking project. I will discuss the thought processes surrounding each major design decision and explain how the design of the application has helped my team reach the very rough and ambitious deadlines that were placed upon us. This entry will focus on leveraging role-based security and how business rules can easily be written using this approach.
After the information modeling phase of the application was complete, I felt it necessary to focus on the rules that will govern the application’s privacy settings and overall user experience. Given the nature of the application, privacy settings were going to be a large part of every operation that we did so it was important that these rules were clearly defined in a central location. While establishing these rules, we realized that the majority of the rules could be define via roles. Let’s take a look at one rule and observe the evolution as we moved towards a role-based approach.
First iteration:
Some User x may create a Comment for some User y’s profile if at least one of the following is true:
- y is equal to x
- x is a friend of y
Role-based version:
Some User x may create a Comment for some User y’s profile if at least one of the following is true:
- y is in the role ‘Self’
- x is in the role ‘Friend’
This isn’t a huge evolution by any means but it introduces artificial roles that the application must be made aware of. It was time to design a solution that could utilize these artificial roles. The first step was to clearly establish the URL patterns used throughout the application. These URLs represent the very base structure - that is, the URLs that are used underneath any URL rewriting. For the purpose of this article, let’s take a look at a user’s profile page URL:
~/Users/Profile.aspx?UserId=x
For the majority of the projects that I’ve worked on in the past, the validation of these rules would occur by making a call to the business layer in the code behind file of the page. This may work for a variety of cases, but I was beginning to find the restrictions of validating these rules at such a late part in the request lifecycle (e.g. location restriction using roles, role aware controls). To overcome this restriction, we introduced the a series of role-based management HttpModules. Knowing the URL structure, we could rest assured that the “UserId” query string parameter will be present for any User-based viewing action (this was placed in a configuration manager to prevent any potential typos). If the UserId parameter is present, then we can execute a custom stored procedure to retrieve a value that indicates the artificial roles that user belongs to and add the principal user to them.
The code to handle these rules was then pulled into a series of ‘RuleValidator’ classes that were code-based representations of the business rules. The aforementioned rule could be represented in code with the following statement:
/// <summary>
/// Returns a flag indicating the result of rule R102:
///
/// Some User x may create a Comment on some User y's profile if at least one
/// of the following are true:
///
/// <list type="bullet">
/// <item>y is equal to x</item>
/// <item>x is in the role 'Friend'</item>
/// </list>
/// </summary>
/// <param name="pCurrentProfile">The <see cref="UserDetail"/> currently being viewed.</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"><paramref name="pCurrentProfile"/> was null.</exception>
public static bool IsAuthorizedToCreateComment(UserDetail pCurrentProfile)
{
if (pCurrentProfile == null)
{
throw new ArgumentNullException("pCurrentProfile");
}
IPrincipal _user = HttpContext.Current.User;
return (_user.IsInRole(SiteRoles.Self) || _user.IsInRole(SiteRoles.Friend));
}
After the initial pattern was created for the role-based manager modules (abstracted out into a base role manager class), it was a piece of cake to implement them for the rest of the application modules (e.g. groups, events, photos). That concludes the role-based injection section of the architecture.
Next up: Role-based web controls.
May 12th, 2008 at 9:01 pm
That is a seriously disturbing yet interesting approach to the permissions problem. So you’re basically reverse mapping your relationships, working from the target user id to the source user id (logged in user), determining their permissions in the context of the target user. Probably only useful in this aspect, since so much of social networking site privacy rules are dependent on relationships between users, but kudos for even messing with ASP.Net security.
May 12th, 2008 at 10:11 pm
@Ben Yes, everything revolves around this notion of the current viewing context. While the User profile example was simple enough, it certainly raises the question of “why”. The more reasonable example to justify this approach is to handle the following relationships:
Photo(.id) is contained within PhotoAlbum(.id).
PhotoAlbum(.id) is owned by User(.id).
PhotoAlbum(.id) is owned by Group(.id).
PhotoAlbum(.id) is owned by Event(.id).
Event is owned by Group(.id).
Given any Photo, the principal User can be in any of the following roles: ‘EventAdmin’, ‘EventMember’, ‘Friend’, ‘GroupMember’, ‘GroupAdmin’, ‘Owner’, ‘Blocked’. By returning a bit-wise combination of values, we can determine all of the appropriate roles and inject them. This allows for easier and more readable business logic which should eventually help with maintenance.
July 29th, 2008 at 9:36 pm
[...] From Zero to launch - Part I [...]