Access Control And Drupal Node Access

Note: the AC code is somewhat deprecated right now, since it is designed mostly to work with mirrored nodes, and CCK does not yet have good support for "by field" access control. But if you want to use it, here's how:

Access control to contacts is probably the most difficult piece of the CiviNode code base, and needs a bit of discussion. Perhaps the best way to start is with the basic scheme, and then a discussion of how this is different from what CiviCRM already does, and how it differs from different schemes used or under discussion for Drupal.

CiviNode ACLs

CiviNode defines a node type, 'civinode_access', that defines a basic unit of access. Since CRM groups are the core way of accessing and organizing contacts in CiviCRM, I've defined an access as the combination of:

  • A requestor group, a CRM group that the current Drupal user belongs to (i.e., the user's CRM contact_id is in that group).
  • A target group, a CRM group of contacts to which the user wants some access.
  • A CRM Profile that restricts access to the fields of the target group; a profile_id of 0 means "all fields of a contact are accessible"
  • Accesses for adding or removing contacts from the target group, and for updating or deleting the contacts themselves.

The access related information is stored in civinode_access, which gets merged into the node at node_load time.

Defining accesses as nodes is just a convenience; it made it easy to manage them. But I don't see any drawbacks in doing this so far. When a more elaborate UI is needed for managing access, it seems to me I can use Views of access nodes to do this for anything I'd expect be needed.

Most of this works via hook_access, and for everything but the 'view' access, it works well. For 'view', it turns out, it doesn't work at all :-( In the end, I swallowed the whole frog and create records in the node_access table on the fly. I also support hook_db_rewrite_sql. This requires me to do a few things that I hate doing:

  • I needed to implement hook_civicrm_post(), the main CiviCRM Drupal call back, to watch for every add/remove of contacts from groups, and see if any nodes that are in the system are affected. So if someone removes a contact from a group in CiviCRM, I need to update the civinode_node_membership table, which keeps track of which CRM groups the underlying CRM contact currently belongs to. This is very ugly, but it's the only way to make sure that only authorized users can see a contact via 'view': Drupal will show your node's content to even anonymous users unless there is a query that will exclude your node. I also needed to watch for when CRM groups were deleted, so I can delete civinode_access objects that become obsolete, and to update civinode_node_membership.
  • I have to maintain a table, civinode_group_memberships, which tracks what groups the current Drupal user belongs to. This is updated at user_load time.
  • I have to implement hook_db_rewrite_sql, which needs to reference civinode_groups_memberships, civninode_access, civinode_node_memberships, node (for $node->type) and node_access in order to strip out civinode_contacts the user should not have access to. If this sounds complex, well, it is :-(

Mostly this code works, although I am still finding SQL errors in some cases. If someone can tell me how to do this in a simpler way, I'm all ears. But from what I've heard from the Drupalers who know the node access system well, I don't think there is one.

Why Not Use na_arbitrator?

Earl Mile's new module, na_arbitrator, has been suggested as one way to handle this problem. I looked at it, and while Earl's scheme is quite clever, it really is designed to handle nodes, and it is designed to handle them via static tables. Therein lies its problem for this application: an external system (CiviCRM) determines who can see what, and in general it isn't possible to construct a query that uses CiviCRM's database tables, which need not be in the same database, or even on the same host. So I need similar tables to the ones Earl implements, but I need somewhat more information about contacts than na_arbitrator will allow for.

There are going to be a number of applications that will be much easier to do thanks to Earl's work. But it doesn't look like CiviCRM related work will be among them.