Patterns Tutorial Series (part 1): RBAC Domain Model

Indeed :slight_smile:

Looks well thought out and clean.

We want the interfaces to be generic so that we can persist the RBAC data in either a database or a file system. That is our next step. How to apply the correct patterns to acheive that level of abstraction. I have seen some of Lastcraftā€™s code implement a Persistent interface which might be the way to go with this. Maybe he would be obliged to go over something like that.

JT

Hiā€¦

I am still trying to reduce the scope so that something can be demonstrated working. I favour use cases over structural specs. for this. We have two use casesā€¦

  1. We ask the authenticator to turn an identity into a set of permissions. These permissions can be queried (they are read only in this context) and return simple boolean values.

  2. An administrator/script can add and remove people and permissions. To save effort, permissions are grouped into roles. A user can have multiple roles in RBAC.

I have already spotted some potential pitfalls in the discussions aboveā€¦

  1. I donā€™t think it is a good idea to have object IDs mixed in. This will entangle the permissions system with application specific stuff. You probably have an idea of ownership and this can be expressed within the application code by adding more permissions. E.g. Have edit_own_documents (author) as well as edit_documents (editor in chief). How these are handled is up to the application - as it should be. Ownership of objects within the application is likely to be complex and highly variable.

  2. Donā€™t involve sessions as the core problem has nothing to do with session management. Basically once you have a set of permissions you can place that set into the session. You no longer need the userā€™s identity because the sesssion system takes care of that for you. You also involve synchronisation issues this way. Because permissions are constant after the login (invariant) they can be cached into the session easily. A user on the other hand may change and you will likely have to fetch it from deep storage on every request.

I am going to shamelessly plug my own tool here and turn the use cases into a test caseā€¦


<?php
class RoleBasedAuthenticationTest extends UnitTestCase {
    function RoleBasedAuthenticationTest() {
        $this->UnitTestCase();
    }
    function setUp() {
        $authorisor = &new Authoriser();
        $authorisor->addUsage('fred', 'secret');
        $authorisor->addRole('pleb');
        $authorisor->addOperation('do_stuff');
        $authorisor->attachRole('fred', 'pleb');
        $authorisor->permit('pleb', 'do_stuff');
    }
    function tearDown() {
        $authorisor = &new Authoriser();
        $authorisor->dropUsage('fred');
        $authorisor->dropRole('pleb');
        $authorisor->dropOperation('do_stuff');
    }
    function testNonUserHasNothingAllowed() {
        $permissions = &$authenticator->('public', '');
        $this->assertFalse($permissions->can('do_stuff'));
    }
    function testBadPasswordHasNothingAllowed() {
        $permissions = &$authenticator->('fred', 'wrong');
        $this->assertFalse($permissions->can('do_stuff'));
    }
    function testLegitimateUserHasActionAllowed() {
        $permissions = &$authenticator->('fred', 'secret');
        $this->assertTrue($permissions->can('do_stuff'));
    }
    function testUserCannotDoNonAction() {
        $permissions = &$authenticator->('fred', 'secret');
        $this->assertFalse($permissions->can('do_unknown'));
    }
}
?>

The permissions class is following the NullValue pattern here. Feel free to play with this spec. though.

Other use cases and test cases can probably be fleshed out. My only thought on persistence is that the queries will be quite involved, but it is otherwise data centric. A PermissionsFinder makes sense in each type of storage. e.gā€¦


$storage = &new Storage(new Configuration());
$finder = &$storage->getPermissionsFinder();
$permissions = &$finder->findByUser($name);

I still feel that the best way to persist the roles and operations is as a single mapping object. This way it is inherently transactional. It is likely to be edited rarely so the overhead of loading the whole thing should be inconsequential. The same can not be said for adding Usages, which are likely more frequent as people may sign up with automated scripts. This gives candidate persistence classes of: Configuration, Storage, AuthorityMapper, UsageMapper, PermissionsFinder, UsageFinder. These multiply up by the ways of storage (DBM files, RDBMS), but are easy to code.

I am avoiding classes for roles and operations as these objects are purely passive data. They would be represented in the storage schema only. This is one of the ways that thinking in data terms rather than in jobs can throw you off track and make things seem more complicated than they really are.

yours, Marcus

The one thing I want to keep from the original spec is the fact that a permission is an operation/object pair. Your permission system mixes the two. edit_own_document is actually a permission to do an ā€˜editā€™ on a ā€˜documentā€™. I think allowing for mixing an matching operations/objects keeps the system flexible and doesnā€™t add too much overhead.

Another thing that is important is the many-to-many relationship between users and roles and the many-to-many relationship between roles and permissions. This makes the system really flexible and avoids redundant data storage. Unfortunately, without using idā€™s, it is difficult to model a many-to-many relationship in say an XML document. You would have something like this:


<?xml version="1.0"?>
<rbac>
  <users>
    <user id="1" name="John" username="user" password="pass"/>
    <user id="2" name="Jane" username="user" password="pass"/>
  </users>

  <roles>
    <role id="1" name="Sales Person"/>
    <role id="2" name="Sales Manager"/>
  </roles>

  <!-- a permission is a mapping between an operation and an object -->
  <permissions>
    <permission id="1" operation="1" object="1"/>
    <permission id="2" operation="2" object="1"/>
    <permission id="3" operation="3" object="1"/>
  </permissions>

  <operations>
    <operation id="1" name="add"/>
    <operation id="2" name="update"/>
    <operation id="3" name="delete"/>
  </operations>

  <objects>
    <object id="1" name="article"/>
  </objects>

  <!-- User Assignment -->
  <user_roles>
    <user_role user="1" role="1"/>
    <user_role user="2" role="1"/>
  </user_roles>

  <!-- Permission Assignment -->
  <role_permissions>
    <role_permission role="1" permission="1"/>
    <role_permission role="2" permission="1"/>
    <role_permission role="2" permission="2"/>
    <role_permission role="2" permission="3"/>
  </role_permissions>
</rbac>

Although the structure realizes the relationships I am going for, it is difficult to manage. It is flexible at the cost of complexity. A RDBMS would be a better candidate for these various many-to-many relationships because then at least you could do efficient joins.

JT

Hiā€¦

Actually I am leaving the separation up to the application, and certainly not mixing them. If anything I am unrolling them. I think mixing ownership IDs with the permissions system is a mistake and the more I read that document the less happy I am with it. Having a User object is the start of rot in my experience. Sounds like the whole set up was pinched straight out of a database or document management system. In that situation the authorisation system would be tightly integrated which is possibly not what we want for a web component.

Itā€™s not the end of the world however. Just a change of character. How about we add object IDs later on, perhaps as an extension?

I think this is an assumption. The only point where the data is really required to be normalised is when an operation is removed across the board. As application code could be broken by this I cannot imagine this being allowed by the system. Likely the fixed list of operations would be shipped with the app. and installed at the start. Again, this doesnā€™t sound like a natural RDBMS solution to me (although it can be done of course). I think RDMBS storage would be chosen by people who have a lot of roles only.

How closely do you want to stick to the spec?

yours, Marcus

We donā€™t have to stick to the spec, but I donā€™t see anything horrible about it.

Two questions:

What is wrong with a user object? To me something like:


if ($user->hasPermission('edit', 'article')) {

}

is self-documenting. A User is a real world entity that has roles and permissions, etcā€¦ It is almost directly codifying the question that we want to ask: ā€œDoes this user have permission to do this operation on this object?ā€

Secondly, what is wrong with unique identifiers?

Thanks,

JT

Hiā€¦

For a library itā€™s scope is too large. For example if you decide to add email contact to your application users where does it go? Sounds like it should go into the same user that is the authentication system. You canā€™t though, as that is supposed to be decoupled from the rest of the system. By simply changing the authorisation systemā€™s name to Usage/UserIdentifier/Fingerprint you have restricted itā€™s scope. Now it is something that can be attached to the user in the application (the one with an email address maybe). It also means that there can be multiple logins for a single person. Having multiple userā€™s for a single person on the other hand just does not make sense.

Another problem I outlined before. You donā€™t ask the user for permission. You ask the authoriser for permissions that you query. That extra step, of turning an entity (user) into a simple value object (permission), really adds lotā€™s of flexibility. It is a safely copyable object for example and is a less complex item to load.

I once saw a User class/table (actually called Subscriber) destroy a whole project. The notion is deceptively vague.

Nothing (?). Do you mean for object IDs that we reference? In that case plenty if we are a library and we are forcing them on an application. Our tendrils are spreading faster than they should. If you can come up with a clean way of introducing them as an optional item, then there is no problem.

yours, Marcus

Here is a simplification of my previous XML document. I merged together operations and objects and I removed any references to IDā€™s.


<?xml version="1.0"?>
<rbac>
  <rbac-users>
    <rbac-user name="John"/>
    <rbac-user name="Jane"/>
  </rbac-users>
  <rbac-roles>
    <rbac-role name="Employee"/>
    <rbac-role name="Manager"/>
  </rbac-roles>
  <rbac-permissions>
    <rbac-permission name="CreateDocument"/>
    <rbac-permission name="RetreiveDocument"/>
    <rbac-permission name="UpdateDocument"/>
    <rbac-permission name="DeleteDocument"/>
  </rbac-permission>
  <rbac-user-roles>
    <rbac-user-role rbac-user="John" rbac-role="Employee"/>
    <rbac-user-role rbac-user="Jane" rbac-role="Employee"/>
    <rbac-user-role rbac-user="Jane" rbac-role="Manager"/>
  </rbac-user-roles>
  <rbac-role-permissions>
    <rbac-role-permission rbac-role="Employee" rbac-permission="RetreiveDocument"/>
    <rbac-role-permission rbac-role="Manager" rbac-permission="CreateDocument"/>
  </rbac-role-permissions>
</rbac>

Please let me know what you think.

Thanks,

JT

Hiā€¦

Looks fine to me. If you use ā€œidā€ fields doesnā€™t that guarantee uniqueness. Are you thinking of storing things this way?

Some rapid hacking using MySql as storageā€¦


class Authenticator {
    ...
    function &getPermissions($username, $password) {
        $this->_storage->getPermissionsFinder();
        return $finder->findByLogin($username, $password);
    }
}
class MySqlPermissionsFinder(&$connection) {
    ...
    function &findByLogin($username, $password) {
        return new Permissions($this->_connection->select(
                'select from permissions, roles, logins where...'));

    }
}
class Permissions {
    var $_operations;

    function Permissions(&$iterator) {
        $this->_operations = array();
        if (! $iterator) {
            return;
        }
        while ($row = $iterator->next()) {
            $this->_operations[] = $row['operation'];
        }
    }
    function can($operation) {
        return in_array($operation, $this->_operations);
    }
}

A sideline: Does anyone know anything about LDAP? I thought it was designed for this kind of thing?

yours, Marcus

Yes, as an alternative to storage in an RDBMS even though an RDBMS seems more suitable in how you can handle relationships.

JT

Letā€™s flesh out the rest of your implementation (if you havenā€™t already) and identify the various design patterns used. I would also like to implement XmlPermissionsFinder as well to use some sort of format as above.

JT

There are two ways to get around this. Have your custom User class extend this User class. So if you want to add an email address and/or other attributes you could:


class MyUser extends User {
    var $email;

    function MyUser($username, $password) {
        parent::User($username, $password);
    }

    function setEmail($email) {
        $this->email = $email;
    }

    function getEmail($email) {
        return $this->email;
    }
}

Or secondly:

You could create a generic mechanism for dynamically attaching attributes to a user object:


class User {
    var $id;
    var $username;
    var $password;
    var $attrs;
    var $authenticated;
    var $permissions;
    var $roles;

    function User($username, $password) {
        $this->username = $username;
        $this->password = $password;
        $this->attrs = array();
    }

    function authenticate() {
        // ... authenticate the user against the database;
    }

    function isAuthenticated() {
        return $this->authenticated;
    }

    function hasPermission($name) {
        if (!isset($this->permissions)) {
            $this->permissions = array();
            // ... get permissions from the database
        }
        return (in_array($name, $this->permissions));
    }

    function hasRole($name) {
        if (!isset($this->roles)) {
            $this->roles= array();
            // ... get roles from the database
        }
        return (in_array($name, $this->roles));
    }

    function setAttribute($name, $value) {
        $this->attrs[$name] = $value;
    }

    function getAttribute($name) {
        return $this->attrs[$name];
    }

    function removeAttribute($name) {
        unset($this->attrs[$name]);
    }

    function hasAttribute($name) {
        return isset($this->attrs[$name]);
    }
}

Then you could create a custom UserFinder which extends the base UserFinder and put whatever attributes you wanted into the User object.

JT

Firstly, your root node indicates that the schema will be limited. If you use an XML document to store configuration information, you will be parsing several XML documents, which would be unnecessary overhead. You would find it much more beneficial to use a schema such as ASP.NETs Web.Config schema. This will expose many benefits for adoptionā€¦

[color=black]

[/color]
 
[color=black]<configuration>
<system.web>[/color]
 
[color=black]	[b][font=Verdana]...[/font][/b][/color]
 
[color=black]<authentication mode="[Windows|Forms|Passport|None]">
	<forms name="[name]" 
		 loginUrl="" 
		 protection="[All|None|Encryption|Validation]"
		 path="[path]" timeout="[minutes]"
		 requireSSL="[true|false]" 
		 slidingExpiration="[true|false]">
		<credentials passwordFormat="[Clear|MD5|SHA1]">
			<user name="[UserName]" 
				 password="[password]"/>
		</credentials>
	</forms>
	<passport redirectUrl="internal"/>
</authentication>[/color]
 
[color=black]<authorization>
	<allow users="[comma separated list of users]"
		 roles="[comma separated list of roles]"/>
	<deny users="[comma separated list of users]"
		 roles="[comma separated list of roles]"/>
</authorization>[/color]
 
[color=black]<identity impersonate ="[true|false]"
		 userName="[domain\\user_name]"
		 password="[user_password]"/>[/color]
 
[color=black]<trust level="[Full|High|Medium|Low|Minimal]" 
	 originUrl=""/>
 
<securityPolicy>
	<trustLevel name="Full" policyFile="internal"/>
	<trustLevel name="High" policyFile="web_hightrust.config"/>
	<trustLevel name="Medium" policyFile="web_mediumtrust.config"/>
	<trustLevel name="Low" policyFile="web_lowtrust.config"/>
	<trustLevel name="Minimal" policyFile="web_minimaltrust.config"/>
</securityPolicy>

[/color]

Defining an Administrator class is an extremely bad in terms of OOD, however as LastCraft has mentioned, you are asking for a headache unless you sit back and clearly design this.

There are a number of components, which make up security:

  • Encryption[]State/less (Cookies)[]HTTP[]Certificates (SSL)[]ā€¦

I must admit I really am not a fan of the above implementation, as it appears that things will become bloated and unethical. Why declare a class for each implementation of MySQL or XML? If this series is trying to demonstrate OO then demonstrate data abstraction and reuse. However, I think a prerequisite would be to give definitions of the terminology used. (Realm, Digest, Nonce)

Now Authentication simply does not stand within the (Application:rolleyes:) use of MySQL and its permissions can come into play to simplify Role Based Authentication & Authorization, which adds countless, advantages. In addition, some common scenarios in security are image verification, resolving DNS, SMTP and as a result expand the specification.

If I can take you back a step, how are you identifying, which pages are secure and require an authentication scheme? You are already trying to implement a PermissionsFinder and skipping a number of stages prior to requiring the knowledge of any Permission. If a security scheme is required, what type of scheme should be applied, HTTP Authentication, Forms Authentication by default? Which digest will be used by default? Page level re-authentication would be required, to add security.

[ot]
Sorry, if it sounds like a rant

Has anyone got any idea whats happening with Pears Auth_Enterprise There has never been anything published, yet the description sounds interestingā€¦
[size=1][color=black]

[/color][/size]
As the name implies, this package aims to provide an enterprise level authentication & authorization service. There are two parts to this package, the service layer which handles A&A requests and a PHP client. Support for other clients (e.g. Java, ASP/VB, etc) is possible further supporting cross-platform enterprise needs. Main features are: 1) Web Service-based 2) implements notion of a Provider which is capable of hitting a specific data store (DBMS, LDAP, etc) 3) Implements a single credential set across a single provider 4) 100% OO-PHP with the client producing a user object that can be serialized to a PHP4 session.


[/ot]

We are focusing our attention on Role-Based Access Control. Access to particular resources is granted based on roles and permissions associated with those roles. We have limited the scope such that authentication returns a set of permissions and the set of permissions is inspected to determine whether the user can access the resource (authorization). One desire that we have is to make the system generic enough to support several persistence mechanisms (e.g. RDBMS, XML, etc.). The goal is to illustrate the use of design patterns such as Domain Model, Data Mapper / Finder, Memento, Iterator, Factory, etc. within the context of a well-constrained problem.

JT

Iā€™ve never implemented it but googling a little tells me that yes :tup: this can be one of the uses of LDAP particularly if you want to have a central mechanism of Authentication and Access Control or for example need to use the same user/login accounts from a NT domain in your web application. I believe LDAP would be a valid storage and in fact a possible alternative of rbacā€¦

As I commented before I was already working on my own version of rbac (some 4 months agoā€¦never finished) and when thinking about how to integrate it to existing or new apps I realized that in order to follow the spec I would need to have some way of storing and/or mapping the application objects and the application users to the rbac system BUT doing so would mean a great loss in flexibility because we would force the programmer to adapt his/her app to our system.

I think this is a critical point and this was precisely what stopped me from going on :frowning:

By reading your posts I agree that to deal with the application objects the best is to simply leave that to the application.

[quote=ā€œā€œseratoninā€ā€]

There are two ways to get around this. Have your custom User class extend this User class. So if you want to add an email address and/or other attributesā€¦[/quote]
mmm I have to say that I donā€™t feel very comfortable with a User class in the rbac systemā€¦why:

  1. for the reasons lastcraft exposed.
  2. I think that the user class (if it exists) would be very specific to the application and almost always used as a model for making CRUD operations in the users table, file or whatever.
  3. that functionality (adding an email address) is a different non related task I mean it doesnā€™t have anything to do with rbac.
  4. also authentication is a very different, subject to change, and sensible part of every application IMHO.

However I still donā€™t see clearly how are we going to communicate with the users or store and map this information to rbac :rolleyes:

I think that if we ever use XML to store rbac information a caching mechanism would be devised also for any user the rbac information would be retrieved only once and then persisted in the session.

Security is a big big part of any application with rbac we only are focusing on access control (authorization) not authentication. The idea is that you should be able to use any kind of authentication.

maybe in order to support different storage mechanism/drivers.

anyway will be playing with your interfaces :stuck_out_tongue:

l8r

Hiā€¦

Do you have an alternate scheme? Writing completely abstract persistence layers is a big job.

Also you can only get reuse if itā€™s there to be had. If you want to take advantage of complex queries and caching (likely here), they will all be quite different. You will be able to get some reuse within the storage families by inheritance (thatā€™s the advantage of the mappers, they bridge to the domain objects). At least this way itā€™s all hidden by the Storage abstract factory.

I like the idea of my code being unethical :). Should I be struck off?

I do agree that my Authorisor class should be something like AccessController and I should take the password out. It is more client code, but it is that little bit more decoupled as oivaf points out.

yours, Marcus

ā€¦more suitable in how you can handle relationships.

Exactly :slight_smile: Sorry Marcus, but I cannot for the life of me, understand your gripe about using a database, though Iā€™ve read every post :eek:

I think for this type of data and itā€™s relationships, XML is bad - This is not what XML was originally designed for.

A question though Marcus, your point on storage, are you suggesting a Memento Pattern ?

I was searching the Web and Binary Cloudā€™s RBAC implementation here:

http://webstract.org/api//__filesource/fsource_binarycloud.auth__authPerm.php.html

JT

Hiā€¦

Well I may have been a bit silly :). Maybe I should be struck offā€¦

The thing is, the point where it needs to be fast is at the point of loading the permission set. This scheme involves a five table join in a DB. It will still work though, and I guess the dataset will not be large.

I am now thinking that a caching scheme may be the way to handle this whilst still using the RDBMS to administer things. The permission sets could be stored as serialised classes in a DBM hash for example. The cache mangement could decorate the finder.

I agree. I am not sure XML is the way to go for storage. However it could be a good way to configure the system, especially for testing or for bootstrapping an application. Basically a loader could parse the XML into a storage solution when it is edited.

I think LDAP is a serious contender as it has very fast reads. RDBMS remove redundancy, but I donā€™t see that as an issue. The main thing is that the issue can be postponed whilst things are built top-down.

I was only thinking of the data transfer between the data mapper and the authentication map so far. It is still rather fuzzy until some more code is written.

yours, Marcus

Iā€™m thinking that we do the JOIN intially and then just cache just the permission set as XML on the server in the session.

JT