Builing an Access Control List Plugin for DataMapper - Part I

What about the idea of transparently integrating an access control list into the model via a plugin. Having domain model instance based security is a requirement for some of the projects I work on. The basic idea is to have a GrantedAuthority resource something like:

  1.  
  2.   class GrantedAuthority
  3.     include DataMapper:Resource
  4.     property :id, Fixnum, :serial => true
  5.     property :user_id, Fixnum
  6.     property :resource_id, Fixnum
  7.     property :resource_type, String    
  8.     property :token, String
  9.   end
  10.  

Then you would modify/shadow some of the class & instance methods on the resource that you are protecting, including:

  • Resource class methods
    • Resource#first / Resource#secure_first / Resource#first! / etc
    • Resource#all
    • Resource#get
    • Resource#[]
    • Resource#create
  • Resource instance methods
    • resource#save
    • resource#destroy

This would allow something along the lines of

  1.  
  2.   @contacts = Contact.all!(user,‘READ’, :first_name.like => ‘J%’, … )
  3.  
  4.   # perhaps the READ token is defined on the Resource
  5.   # and you don’t need to specify it here
  6.   @contacts = Contact.all!(user, :first_name.like => ‘J%’, … )
  7.  

The model should “know” how to create the necessary joins to apply the ACL

  1.  
  2.   – #all SQL
  3.   SELECT * FROM contacts
  4.  
  5.   – becomes
  6.   SELECT * FROM contacts WHERE (id IN
  7.       (SELECT resource_id
  8.        FROM granted_authorities
  9.        WHERE user_id = ? AND token = ? AND resource_type = ?
  10.        )
  11.    )
  12.  

This has been fairly simple so far. Lets allow for a more complex case of the resource being queried not being the resource that has the entry in the granted_authorities table. For example, an OrganisationalUnit that contains a number of Contact(s) and the right to ‘READ_CONTACT’ is granted on the OrganisationalUnit. We would need to produce something like this:

  1.  
  2.   SELECT contacts.*, organisational_units.id FROM contacts
  3.   LEFT OUTER JOIN organisational_units ON organisational_units.id = contacts.id
  4.   WHERE (organisational_units.id IN
  5.       (SELECT resource_id
  6.        FROM granted_authorities
  7.        WHERE user_id = ? AND token = ? AND resource_type = ?
  8.        )
  9.    )
  10.  

What does dm-core need to provided for us to build the plugin? We need to support joins to join in the container objects. Dan Kubb is working on that and should have it pretty soon. We must be able to do sub-selects. This feature was push to after the 1.0 release. Fortunately, it was not so hard to implement. I just pushed the required changes to github. You can now do something like:

  1.  
  2.       acl = DataMapper::Query.new(Permission, :user_id => 1, :resource_type => ‘SailBoat’, :token => ‘READ’, :fields => [:resource_id])
  3.       query = DataMapper::Query.new(SailBoat, :port => ‘Cape Town’,:id => acl,:captain.like => ‘J%’)
  4.       boats = @adapter.read_set(repository(:sqlite3),query)    
  5.  

Once I wrap this concept up into a plugin you would do something like:

  1.  
  2.   user = get_current_user
  3.   contacts = Contact.all!(user,‘READ’,:first_name => ‘john’,:city.like => ‘Jo%’)
  4.  
  5.   # or if the token was specified on the Resource
  6.   contacts = Contact.all!(user,:first_name => ‘john’,:city.like => ‘Jo%’)
  7.  

I will write about how you would configure ACL’s on you model/resource in a part II.

– until next time –

One Response

  1. Guyvdb.info » Blog Archive » Builing an ACL Plugin for DataMapper - Part II Says:

    […] time I wrote about the internals of a proposed ACL plugin. Today I want to focus on the model. How would you […]

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.