Question

Introduction

I'm currently building an access control system in my DataMapper ORM installation (with CodeIgniter 2.*). I have the initial injection of the User's rights (Root/Anonymous layers too) working perfectly. When a User logs in the DataMapper calls that are done in the system will automatically be marked with the Userrights the User has.

So until this point it works perfectly, but now I'm a bit in a bind. The problem is that I need some way to catch and filter each method-call on the Object that is instantiated.

I have two special calls so I can disable the Userrights-checks too. This is particularly handy at the exact moment I want to login a User and need to do initial checks;

DataMapper::disable_userrights();

$this->_user = new User($this->session->userdata('_user_id'));
$this->_userrights = ($this->_user ? $this->_user->userrights(TRUE) : NULL);

DataMapper::enable_userrights();

The above makes sure I can do the initial User (and it's Userrights) injection. Inside the DataMapper library I use the $CI =& get_instance(); to access the _ globals I use. The general rule in this installment I'm building is that $this->_ is reserved for a "globals" system that always gets loaded (or can sometimes be NULL/FALSE) so I can easily access information that's almost always required on each page/call.

Details

Ok, so image the above my logged-in User has the Userrights: Create/Read/Update on the User Entity. So now if I call a simple:

$test = new User();
$test->get_where('name', 'Allendar');

The $_rights Array inside the DataMapper instance will know that the current logged-in User is allowed to perform certain tasks on "this" instance;

protected $_rights = array(
    'Create' => TRUE,
    'Read' => TRUE,
    'Update' => TRUE,
    'Delete' => FALSE,
);

The issue

Now comes my problem. I want to control these Userrights by validating them over each action that is performed. I have the following ideas;

  1. Super redundant; make a global validation method that is executed at the start of each other method in the DataMapper Class.
    • Problem 1: I have to spam the whole DataMapper Class with the same calls
    • Problem 2: I have no control over DataMapper extension methods
    • Problem 3: How to detect relational includes? They should be validated too
  2. Low level binding on certain Core DataMapper calls where I can clearly detect what kind of action is executed on the database (C/R/U/D).

So I'm aiming for Option 2 (and 1.) Problem 2), as it will also solve 1.) Problem 2.

The problem is that DataMapper is so massive and it's pretty complex to discern what actually happens when on it's deepest calling level. Furthermore it looks like all methods are very scattered and hardly ever use each other ($this->get() is often not used to do an eventual call to get a dataset).

So my goal is:

  • User (logged-in, Anonymous, Root) makes a DataMapper istance
    • $user_test = new User;
  • User wants to get $user-test (Read)
    • $user_test->get(1);
  • DataMapper will validate the actual call that is done at the database
    • IF it is only SELECT; OK
    • IF something else than SELECT (or JOINs to other Model that the User doesn't have access to that/those Models, it will fail with a clear error message)
    • IF JOINed Models also validate; OK
  • Return the actual instance;
    • IF OK: continue DataMapper's normal workflow
    • IF not OK: inform the User and return the normal empty DataMapper instance of that Model

Furthermore, for this system I think I will need to add some customization for the raw_sql (etc.) SQL calls so that I have to inject the rights manually related to that SQL statement or only allow the Root User to do those things.

Recap

I'm curious if someone ever attempted something like this in DataMapper or has some hints how I can use/intercept those lowest level calls in DataMapper.

If I can get some clearance on the deepest level of DataMapper's actual final query-call I can probably get a long way myself too.

Was it helpful?

Solution

I would like to suggest not to do this in Datamapper itself (mainly due to the complexity of the code, as you have already noticed yourself).

Instead, use a base model, and have that extend Datamapper. Then add the code to the base model required for your ACL checks, and then overload every Datamapper method that needs an ACL check. Have it call your ACL, deal with an access denied, and if access is granted, simply return the result of parent::method();.

Instead of extending Datamapper, your application models should then extend this base model, so they will inherit the ACL features.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top