views:

154

answers:

1

Yes, this has been asked before here and here. I'm curious as to whether the approach I'm considering is architecturally sound.

Let me start off by trying to describe what I'd like to be able to do with my object model:

class Person {
  ISet<Roles> Roles { get; set; }
}

class RoleDefinition {
  string Name { get; set; }
}

class RoleAssignment {
  RoleDefinition Definition { get; set; }
  Person Person { get; set; }
}

class UserRole : RoleAssignment {
  public virtual string Login { get; set; }
  public virtual string Password { get; set; }
}

With the intent being to be able to work with roles in the following manner:

// Find all "users" with a matching login
from user in userRolesRepository.FindAll(u => u.Login.StartsWith("abc")) 
select user.Person;

To do this, I'm considering the following data model

Person table (Id, Name)
RoleDefinition table (Id, Name)
RoleAssignment table (Id, DefId, PersonId)
UserRole table (RoleAssignmentId, Login, Password)
AdminRole table (RoleAssignmentId, ...)

I'll map UserRole and AdminRole as joined-sublass to RoleAssignment in NHibernate.

So, that's a 1:1 between Person and UserRole and AdminRole, a 1:1 between UserRole and RoleAssignment, and an n:1 between RoleAssignment and RoleDefinition.

My question is this: Is this really a good model?

Are there better ways to model this without losing the ability for each role to have strongly typed, queryable properties? How well will it scale, considering I will be adding even more roles to the system as we move along?

+3  A: 

At first glance, I think it's a bit odd for a single user to have multiple logins and passwords, one for each role, unless you will assume that a user always belongs to a single role. For example, if I had both belonged to roles named Accountant and Salesperson, such as might happen in a small business, it would seem by the definition above that I would have two RoleDefinitions and, as such, two logins and passwords.

Aside from that, in the past, I have mapped this similarly. There is a User class, which is essentially a user profile and has properties such as string UserName, string HashedPassword, TimeZoneInfo TimeZonePreference, ISet<Role> Roles, etc, as well as a LogOn(string password) method.

The LogOn() method of my User class does things like update the FailedLogonsCount property or TemporaryLockoutLiftedAtUtc property and so forth depending on whether or not hashing the passed in password succeeds against the one stored, or it returns a non-persisted object that implements IPrincipal, which is a standard .NET interface.

In this sense, I distinguish between the user's profile (the User class) and their authentication/authorization tokens (non-persisted classes that implement IPrincipal and IIdentity so that they can participate in the various [Authorize] and Thread.CurrentPrincipal schemes used throughout the .NET framework). When the User instance creates the object that implements IPrincipal, it just passes a copy of the user's roles as an array of strings so that the IPrincipal's IsInRole() method will work.

This means that my role names are essentially magic, well-known strings, in effect being a unique key in a Roles database table. But I don't think there's much of a way around that. Indeed, my Role class looks like this:

class Role {
int? Identity { get; }   // database identifier that I never really look at
string RoleEnum { get; } // the "enumeration" that is 
                         //   the well-known string, used in IsInRole()
string RoleName { get; } // a human-friendly, perhaps 
                         //   localizable name of the role
}

I don't have separate subclasses for each role type. Do UserRole and AdminRole as classes really have separate behavior intrinsically? I would submit that they are merely different data points of a generic Role class, so you don't need separate subclasses for each of them.

When you add a role to your system, you are either going to have re-compile the whole shebang with updated checks for that role (this is what I do, I don't expect to add a role frequently), or, if you really wanted to get fancy, the Role class could have a set of Permission objects or some such within them, and your code could just ask the role if role.AllowsUserToDoThis() and have all the permissions checking in one place. Configuring the role's set of Permissions would, therefore, allow you to edit fine-grained access control as the system is running, but you would lose the simplicity of the .NET-provided role-based security model.

There is, as you might have guessed, a million ways to do it, but hopefully this is some helpful feedback. Good luck!

Nicholas Piasecki
Thanks, Nicholas, for the detailed answer. It's not clear from my original problem description, but the idea was to make it possible for roles to have queryable properties. A more concrete example from my specific scenario would be a "User" role that has login and password, and a "Student" role that has a list of classes assigned. A person may be a user and/or a student and I'd be able to query for either role with their properties. I'll add constraints so a role can be assigned to a user only once, so a person would never end up with two logins and passwords.
Ragesh