Patterns Tutorial Series (part 1): RBAC Domain Model

For our first collaborative effort it has been suggested by several users that we implement a Role-Based Access Control system using Design Patterns and Best Practices. There is an official ANSI spec for Role-Based Access Control here:

http://csrc.nist.gov/rbac/rbac-std-ncits.pdf

Approach:

I think we should start with the domain model and then move onto the data model (database design) which will include a mapping between the two. Once we have a solid Domain Model and Data Model we can start talking about whether we want a service layer, what the application layer would look like, and finally the presentation layer. I think that making a service layer would be nice enhancement because then we could have a Web-based presentation layer and build some Web services on top as well. I think we could purposefully start out simple and go through some refactoring to illustrate how that process fits into the picture.

So, let's start with the domain model.

JT


Update:

Here is the progress that has been made so far:

We started off by looking at the NIST RBAC specification. Realizing that the spec was way too large for our purposes, we decided to focus on the core RBAC. We removed the concept of sessions and concentrated on the relationship between Users, Roles, and Permissions. It was proposed that we have a User class but ultimately we decided that the scope of a user class would be too large. Instead, we would have a Authorizer class which returned a set of permissions. The set of permissions would be inspected to determine whether or not a user had permission to do a particular operation. An interface and some test cases were created and we are now at the point where we will begin to flesh out the classes and indentify patterns that will be used in the implementation.

The core spec identifies the following entities:

User
Role
Object
Operation
Permission
Session

There is a many-to-many relationship between Users and Roles
There is a many-to-many relationship between Roles and Permissions.
There is a one-to-many relationship between Users and Sessions.
There is a many-to-many relationship between Sessions and Roles.
There is a many-to-many relationship between Objects and Operations.
There is a containment relationship between Permissions and (Objects and Operations).

A session is really just a set of active roles for a particular user.

Does anybody want to whip up a quick UML diagram to illustrate this or is the drawing in the spec sufficient? This is a nice spec BTW.

Thanks,

JT

I think maybe is not that necessary as you commented the spec is very good (y) but maybe including a diagram from the spec for example

could do ?

The application will need to do the user assignments and permission assignments as well as CRUD for each entity identified above.

JT

A permission is really just a container of objects and operations on those objects. So we want to be able ask the system questions like:

Does this user have permission to do this operation on this object?

if ($user->hasPermission('edit', 'article')) {
    // do edit on article
} else {
    // error: you do not have permission to edit this article
}

JT

agreed :tup: .

Now what about using the proposed function/method names and prototypes by the spec ?

...

I believe that the only tricky part of this, considering that there is already an evolved spec, is to come up with a solution or implementation that is flexible, highly reusable and adapted to target the web development environtment.

more later....

Your preliminary schema looks good. I was hoping that we could tackle the object model first and then proceed to the database design, but it appears that you have captured the core relationships in your data model.

JT

So we have the following entities for core RBAC:

User
Role
Permission
Operation
Object
Session

The spec also defines the following "functions":

Administrative commands:

AddUser
DeleteUser
AddRole
DeleteRole
AssignUser
DeassignUser
GrantPermission
RevokePermission

Supporting system functions:

CreateSession
DeleteSession
AddActiveRole
DropActiveRole
CheckAccess

Review functions:

AssignedUsers
AssignedRoles

Advanced review functions:

RolePermissions
UserPermissions
SessionRoles
SessionPermissions
RoleOperationsOnObject
UserOperationsOnObject

So now we have the task of determining the classes, attributes, and methods from this information.

JT

Hi...

Guys, please stop! :eek:

You've started off with a huge spec, but no goal or context. You will end up with a design that is overkill and then takes ages to code. Modern development is iterative, not waterfall, and this could turn into a worst practices thread rather than a best practices one. Already it's too complicated for just about any web app. I've ever seen. Here are some places where things have gone overboard...

The spec includes methods for setting up roles and permissions for a continually running system. With PHP you can do that by editing a file. It looks very much to me like a spec. for a centralised authentication and authorisation server, rather than a single application. Is that what you want? If not then just edit a single web page and so doing ditch over half the methods.

What do you want the client code to look like? The only operation an authorisation system ultimately has to accomplish is this...

$permissions = $authenticator->getPermissions($username, $password);
if ($permissions->can_edit()) {
    ...
}

Everything else is complication and coupling. The management of the logins would be handled by a separate interface, say an authorisor.

You are jumping straight into a model. Who says the essential entities are those listed above? They may exist as something (being conceptual they probably will), but that something could just be the act of one referencing the other. You have started with a relational DB schema. Who says we need a relational database? Authorisation data is read often, but written rarely. This opens up lot's of possibilities. It is probable that an RDBMS system will still be used, but it is still an implementation decision that can be put off.

Just suppose that we do indeed want an authentication system that could be remoted. Here is one facade and one memento that will do the trick just following the spec. given...

class Authoriser {
    function Authoriser() { ... }
    function getOperations($username, $password) { ... }
    function addUsage($username, $password) { ... }
    function dropUsage($username);
    function addRole($role) { ... }
    function dropRole($role) { ... }
    function grantOperation($role, $operation) { ... }
    function revokeOperation($role, $operation) { ... }
}
class Permissions {
    function Permissions(&$authoriser, $username, $password) {
        $this->_allowed = $authoriser->getOperations(
                $username, $password);
    }
    function __call() { ... }
}

Because the permissions are read only after they are fetched, the getOperations() method can pass a simple list (memento pattern) that the dynamic proxy can make into an object. Everything else can be either a SOAP interface or a local interface without affecting the client code. In fact even simpler is to replace all of the twiddly granting and revocation with a simple XML list of roles and operations...

<?xml?>
<rbac version="214">
    <role name="superuser">
        <operation name="edit"/>
    </role>
</rbac>

Now the first part of the interface is...

class Authoriser {
    function Authoriser() { ... }
    function getOperations($username, $password) { ... }
    function addUsage($username, $password) { ... }
    function dropUsage($username) { ... }
    function queryAccessList() { ... }
    function commitAccessList($xml) { ... }
}

This has the advantage of making the access system more resilient to changes later on.

This is all worst case of course. No one has said that we will ever need remote access.

yours, Marcus

I believe the goal here should be to implement the Core RBAC spec. The spec is huge, we only need the core for now. The core is pretty simple as I have outlined above. What I think we need to do is define what classes we are going to have and how the operations fit into those classes. I already have a DomainModel in my head that should accommodate the core RBAC using the entities above. If we find that the entities are insufficient, we can always create different one's. My idea was to start with the spec and diverge if necessary.

JT

Edit.

Here is a set of simple interfaces and some ideas of where I think the various functions belong.

// User class will contain a set of Sessions.
// User Assignment (UA) associates this User with a particular Role.
class User {
    function User($name) {}
    function createSession() {}
    function deleteSession(&$session) {}
    function assignUser(&$role) {}
    function deassignUser(&$role) {}
}

// Session class will have a set of active Roles.
class Session {
    function Session($name) {}
    function addActiveRole(&role) {}
    function dropActiveRole(&role) {}
    function checkAccess($operation, $object) {}
}

// Role class will have a set of Permissions.
// Permission Assignment (PA) says that Permissions are granted and revoked to/from Roles.
class Role {
    function Role($name) {}
    function grantPermission(&$permission) {}
    function revokePermission(&$permission) {}
}

// A Permission is simply an Operation on an Object.
class Permission {
    function Permission($operation, $object) {}
}

// The client code would look something like this:
$session =& $user->createSession();

if ($session->checkAccess('edit', 'article')) {
    // user is allowed to edit the article
} else {
    // error
}

Comments on this design are welcome (appreciated).

JT

Hi,
first of all I don't have much experience so pardon my (future) mistakes :D, as lastcraf says maybe we should state what is going to be the goal or context for this project or tutorial or whatever :tup:

I never thought to implement all the entire spec but instead adapt it to web applications. I believe the client code will be very similar to the one seratonin and lastcraft posted.

By skimming the spec you can realize that there are at least 2 sets of functions for core rbac: one for the end user and one for the rbac system administration and yes you could say it is very basic and easy to implement because it is. The complexity increases with the other levels of rbac like Hierarchical RBAC, SSD relation and DSD relation for which I still haven't seen a use in web development.

I really find it very easy to code an rbac system the wrong way so this thread is a great oportunity to do the oposite smiley

So where should we start ? maybe a list of requirements ? ......mm I'll remove the db schema wink

First I think we should divide the functions/classes on

1) client side - the programmer who wants to implement rbac on a new or existing application

2) administration side - this is the interface that interacts with the objects and users of our new or existing application, creating, editing, assigning roles, creating and assigning permissions, etc.

To clarify an object could be for example: the authoring suite of Serendipity then the possible operations would be create, edit or delete entries.

Now about the interfaces ... on the client side

ideally IMO our rbac implementation shouldn't have to know anything about authentication nor sessions. You could authenticate to an LDAP server or via SOAP, or you could have file or db sessions with you own Session class. I know you can have wrappers to integrate everything but I think that that task should be left to the end user.

So for example using the proposed functions we just need:

/* mm what about using functions instead of a class ??? */
class RBAC {
    /***
    * LoadRoles should only be called once commonly at the start of
    * an authenticated user session
    * @param UserID - your mapping to your users system/table
    */
    function &LoadRoles($UserId) {}
    function CheckAccess(&$session, $operation, $object) {}
}

// some very basic simplistic example for an application that requires
// a user to be authenticated
session_start(); // or Session :: start() or $session->start :p
if (!$_SESSION['auth']) {
    require 'auth.inc.php';
    $authenticator =& new Authenticator($username, $password);
    if ($authenticator->isAuthenticated()) {
        $_SESSION['permissions'] =& RBAC::LoadRoles($_SESSION['user_id']);
    }
}

......

//later in other part of the app
if (RBAC::CheckAccess($_SESSION['permissions'], 'add', 'comment')) {
    // do it
}

what do you think about this ?

seratonin :
in your Session interface regarding your methods addActiveRole and dropActiveRole, I don't think we're going to need those because usually a user is authenticated only once and at that moment all the roles corresponding to the user are loaded (is this a good or bad thing ?). Also I believe that this methods are part of the higher levels of RBAC wink

about your user and session interfaces see my comments above and tell me what you think smiley

I never thought to implement all the entire spec but instead adapt it to web applications.

:agree:

As Marcus has said - my interpretation - to implement the whole specification would be way over the top ?

Also, I think the original purpose of this thread, we should remain OO all the way, to design with OO and Proc Prog would proberly do more harm than good for those who are interested in learning OO Design, Design Patterns and Refactoring, et al.

smile

//later in other part of the app
if (RBAC::CheckAccess($_SESSION['permissions'], 'add', 'comment')) {
    // do it
}

Not to happy with this. In my view, it could be a class method that fetches the permissions, as it is not set in stone that you'd be using SESSIONs ?

This requirement could change from application to application is what I'm saying I suppose smile

Let's say we started off with a more course-grained, "service-oriented" approach and we treated the core RBAC functional specification as a set of "use cases".

We would have:

class RBAC {
    // Admin Functions
    function AddUser($name) {}
    function DeleteUser($name) {}
    function AddRole($name) {}
    function DeleteRole($name) {}
    function AssignUser($user, $role) {}
    function DeassignUser($user, $role) {}
    function GrantPermission($object, $operation, $role) {}
    function RevokePermission($object, $operation, $role) {}

    // System Functions
    function CreateSession($user, $session) {}
    function DeleteSession($user, $session) {}
    function AddActiveRole($user, $session, $name) {}
    function DropActiveRole($user, $session, $name) {}
    function CheckAccess($session, $operation, $object, $result) {}

    // Review Functions
    function AssignedUsers($role) {}
    function AssignedRoles($user) {}

    // Advanced Review Functions
    function RolePermissions($role, $result) {}
    function UserPermissions($user, $result) {}
    function SessionRoles($session, $result) {}
    function SessionPermissions($session, $result) {}
    function RoleOperationsOnObject($role, $object, $result) {}
    function UserOperationsOnObject($user, $object, $result) {}
}

Obviously that is quite a "god" class, so we could break it down further into (where the classes are more like groups of related transaction scripts):

class RBACAdmin {
    // Admin Functions
    function AddUser($name) {}
    function DeleteUser($name) {}
    function AddRole($name) {}
    function DeleteRole($name) {}
    function AssignUser($user, $role) {}
    function DeassignUser($user, $role) {}
    function GrantPermission($object, $operation, $role) {}
    function RevokePermission($object, $operation, $role) {}
}

class RBACSystem {
    // System Functions
    function CreateSession($user, $session) {}
    function DeleteSession($user, $session) {}
    function AddActiveRole($user, $session, $name) {}
    function DropActiveRole($user, $session, $name) {}
    function CheckAccess($session, $operation, $object, $result) {}
}

class RBACReview {
    // Review Functions
    function AssignedUsers($role) {}
    function AssignedRoles($user) {}
}

class RBACAdvancedReview {
    // Advanced Review Functions
    function RolePermissions($role, $result) {}
    function UserPermissions($user, $result) {}
    function SessionRoles($session, $result) {}
    function SessionPermissions($session, $result) {}
    function RoleOperationsOnObject($role, $object, $result) {}
    function UserOperationsOnObject($user, $object, $result) {}
}

Even that still seems too procedural for my taste. I know that we might want a service layer later, but I what I was trying to do is come up with a Domain Model -- something that encapsulates both the data and the behavior of the system which brought me to create the set of interfaces in my previous post. Now, I can see how you would create permissions, assign those permissions to a role and then assign roles to users with that model, but it is still lacking and that is where I was hoping others might help improve it.

Thanks,

JT

After reviewing the original specification and the various posts, I think we should refine/descope the spec a little bit. As Marcus stated in his post, ultimately the client needs to be able to ask the system the following question:

"Does this user have permission to do this operation on this object?"

The original spec calls for "sessions" but I don't think that is necessary for our treatment. I think we should focus on User, Role, and Permission (where a permission contains an operation and an object). Now, typically there are two stages, authentication and authorization. Authentication usually happens once and Authorization will happen many times once the user has been authenticated. So if we want to include authentication, our system must also ask:

"Is the user authethenticated?"

and also provide some mechanism to authenticate the user.

We could have two stateless classes that are responsible for these two activities:

Authenticator
Authorizer

Or we could somehow integrate the behavior directly into our Domain Model. I think that we can expose the functionality with Authenticator and Authorizer interfaces but have then directing calls to the Domain model (User, Role, Permission) which behind those interfaces. Now keep in mind I have not even begun to think about the admin functions (create user, create role etc).

JT

I can see how you would create permissions, assign those permissions to a role and then assign roles to users with that model, but it is still lacking and that is where I was hoping others might help improve it.

I would from this quotation, create 3 classes from it, no ? ie

CreatePermissions { ... }
AssignPermissions { ... }
ManageRoles { ... }

As, for example, when you create a permission, you'd then need to be given to a role(s) - which has nothing to do with Roles in it's self, it's a standalone entity as I see it smile

Just my thoughts.

I like the idea of breaking things up a bit so that we don't have any "god" classes but those classes (at least by the names) sound more like they should be a group of related domain logic in some domain model. Okay. So we have these administrative functions. Who or what would normally take care of these functions? Most likely it would be a system administrator. If we put the functionality in the User Domain Model, then you will have a bunch of functionality that is not used by 99% of the users. Where to put the administrative functions... Well, we could have an Administrator class which has the functions addUser, deleteUser, addRole, deleteRole, assignUser, deassignUser, grantPermission, revokePermission. So we would have three classes:

class User {
    function User($username, $password) {}
    function authenticate() {}
    function isAuthenticated() {}
    function hasPermission($operation, $object) {
        $flag = false;

        foreach ($this->permissions as $p) {
            if ($p->getOperation() == $operation && $p->getObject() == $object) {
                $flag = true;
            }
        }

        return $flag;
    }
}

// read-only container
class Permission {
    var $operation;
    var $object;

    function Permission($operation, $object) {
        $this->operation = $operation;
        $this->object = $object;
    }

    function getOperation() {
        return $this->operation;
    }

    function getObject() {
        return $this->object;
    }
}

class Administrator {
    function addUser($user) {}
    function deleteUser($user) {}
    function addRole($role) {}
    function deleteRole($role) {}
    function assignUser($user, $role) {}
    function deassignUser($user, $role) {}
    function grantPermission($role, $permission) {}
    function revokePermission($role, $permission) {}
}

// client code (administrative functions)
$admin =& new Administrator();

// create a new user
$admin->addUser('John', 'username', 'password');

// create a new role
$admin->addRole('author');

// create a permission (operation/object pair)
$perm =& new Permission('edit', 'article');

// grant the permission to the new role
$admin->grantPermission('author', $perm);

// assign the role to the user
$admin->assignUser('John', 'author');

// client code (general)
$user =& new User($username, $password);
$user->authenticate();

if ($user->isAuthenticated()) {
    if ($user->hasPermission('edit', 'article')) {
        // do edit
    } else {
        // error
    }
} else {
    // redirect to login
}


It seems like a simple enough interface...

JT