views:

161

answers:

2

I'm working on a PHP application, and I'd like to add access control to some of my objects. I didn't tag this question as PHP, as I feel this question is not language specific.

Say I have a 'Service class'

abstract class Service {


}

Many services use this as a baseclass. One pseudo example would be:

class Companies extends Service {

  function getCompanyInfo($id) {
      //...
  }

}

Later down the road I want to add access control. The example 'getCompanyInfoById' method is a 'read' operation, so this would require a 'read' privilege.

At this point I can implement this in the following way:

  1. Add accesscontrol to the Service class. Every method (such as getCompanyInfoById) must call the 'hasPrivilege' method internally before completing the operation and returning the result.
  2. Wrap all Service objects in a some kind of Proxy object that will checks privileges before calling the method in the inner-object.
  3. Completely separate access control, and force the 'caller' to check privileges before calling the method.

Cons for every option:

  1. This requires changing all Services, and requires them to be aware of Access control. I feel this goes against Separation of concerns.
  2. This breaks OOP features, such as Polymorphism. The caller no longer knows what interfaces any service supports.
  3. This is the most flexible, but the big drawback is that checking permission is now implicit. Developers can 'forget' or complex codepaths can cause unauthorized services to be called.

Are there better ways to approach this altogether?

+1  A: 

The JEE model is pretty much on the lines of 2. Your code runs in a "Container", you tell the container about your interface entry points (URLs for servlets, methods for EJBs) and define the roles that can use these entry points. An adminstrator maps the authentication info (eg. LDAP user and groups) to specific roles and the container consults that mapping in granting access to the entry points.

The key here is that the Container "knows" about your code, it's effectively a quite clever proxy.

In the absence of a container I'd be looking at the proxy approach, perhaps using some kind of Aspect Oriented technique.

I think you're right that option 3 is very brittle,too much responsibility on the client programmers.

djna
+1  A: 

Another solution could be a little variant of your 1.

ex.

class Service
{
  var $ACL = //some hash map with acl
}

class Companies extends Service
{

  function getCompanyById($id)
  {
    //real code
  }
}

class SafeCompanies extends Companies
{
//If a method must be "protected" with an ACL, you must override them in this way
  function getCompanyById($id)
  {
    $this->check('read'); //raise an exception if current user haven't READ privilege
    parent::getCompanyById($id);    
  }  
}

in this way you dont mix responsibilities and still can use polymorphism

my 2 cents

Daniele Teti
This is more or less what I was leaning towards.
Evert