I appreciate the response TomB, it has helped tremendously.
I’ll explain a little about what I’m trying to accomplish and see if people can suggest an improvement for managing the permission system.
I have a content management framework very similar conceptually to Drupal. At this moment the CMF has a variety of entities within it. Those entities are listed below – though the amount is sure to grow.
- node types
- navigation
- navigation links
- sites
- vocabulary
- terms
- config
Based upon what I’m seeing and have been researching it seems appropriate to to assume that all these low level entities will have a standard set of permissions:
- ADD
- EDIT_OWN
- EDIT_ALL
- DELETE_ALL
- DELETE_OWN
- READ_ALL
- READ_OWN
So, I was thinking that all entities would automatically inherit or have these permissions by default.
Now, as far as storing them in the data base my thoughts were to create a table with the below fields.
- sites_id
- group [user,role]
- groups_id
- entity [node_type,navigation,navigation_link,site,term,vocabulary,…]
- entity_id (default NULL)
- permissions (binary rep. of permissions)
- revoked (binary rep. of user revoked permissions - regardless of users roles)
The idea here being that I can assign permissions to a role or user using a single table. In addition I can go even further and specify an entity id. The entity id would be a foreign key reference to the table that the entity type maps to. For example, there may be a a node type like “private documents”. So I could assign specific permissions to that node type by referencing the node_type entity and its primary key for “private documents” node type. Similarly, I could control permissions on individual terms, vocabularies, etc.
This could be all stored in the single table for users and roles due to having a group column which tells me the permission group - user or role. User being an individual with directly assigned permissions and role being a role that may be assigned to a user. If the group is role than the foreign key groups_id would reference a role, if it is a user it will reference a user. That seems to make things pretty flexible as I could even add an additional layer besides role and user, if needed.
Does all this sound like a decent approach? I’m more familiar with using strictly role based systems in previous projects but am looking for something a lot more flexible here.
The below was actually my original plan, using a standard ACL. However, after thinking about it having been working on other things for a while I don’t think my initial instinct is the best way to about this.
<?php
/*
* Access control list data access layer
*
*/
$this->import('App.Core.DAO');
class MCPDAOACL extends MCPDAO {
private
/*
* In an ideal world this class would be transient
* but storing the permissions here prevents multiple
* trips to database when checking permissions for the current
* user for the current site.
*/
$_arrPermissions;
public function __construct(MCP $objMCP) {
parent::__construct($objMCP);
$this->_init();
}
private function _init() {
/*
* Set current users permissions for the current site
*/
$this->_arrPermissions = $this->getPermissions(
$this->_objMCP->getSitesId()
,$this->_objMCP->getUsersId()
);
}
/*
* Determine whether user has permission to do something or access area
*
* IMPORTANT: This method uses the permissions loaded when class was instantiated. This
* is done to reduce the load on the database but makes way for possible problems when
* updating permissions, roles and/or detecting permissions for other users or based
* on other sites. Due to this one should only use this method for detecting permissions
* of the current logged in user for the current site. Permission checking for other sites
* and users should be done in a separate method (getPermissions). It is safe to use method
* after saving new permission(s) for the current user under the current site because the permission
* cache will be reloaded when those items change. However, that will only occur for the current site
* and current user. Any other type of checking needs to be done else where.
*
* Always remember this method answers this question:
*
* Does the current user have the given permission for the current site?
*
* @param str permission constant
* @return bool yes/no
*/
public function hasPermission($strPermission) {
return in_array($strPermission,$this->_arrPermissions,true); // CASE SENSITIVE !!!
}
/*
* Gets all users permissions (real-time)
*
* @param int sites id
* @param int users id
* @return array users permissions
*/
public function getPermissions($intSitesId,$intUsersId) {
/*
* Build query
*/
$strSQL = sprintf(
'SELECT
r2p.permission
FROM
MCP_ACL_USERS_TO_ROLES u2r
INNER
JOIN
MCP_ACL_ROLES_TO_PERMISSIONS r2p
ON
u2r.roles_id = r2p.roles_id
LEFT OUTER
JOIN
MCP_ACL_USERS_TO_PERMISSIONS u2p
ON
u2r.users_id = u2p.users_id
AND
r2p.permission = u2p.permission
WHERE
u2r.users_id = %s
AND
(u2p.deny IS NULL OR u2p.deny = 0)
GROUP
by
r2p.permission
UNION
SELECT
u2p2.permission
FROM
MCP_ACL_USERS_TO_PERMISSIONS u2p2
WHERE
u2p2.users_id = %1$s
AND
u2p2.deny = 0'
,$this->_objMCP->escapeString($intUsersId?$intUsersId:0) // when user is not logged in return 0
);
/*
* Flatten array for easy in_array() usage to check for permissions
*/
$arrPermissions = array();
foreach($this->_objMCP->query($strSQL) as $arrRow) {
$arrPermissions[] = $arrRow['permission'];
}
/*
* Add magical logged in permission
*/
if($this->_objMCP->getUsersId() && !in_array('LOGGED_IN',$arrPermissions,true)) {
$arrPermissions[] = 'LOGGED_IN';
}
return $arrPermissions;
}
}
?>
This was the wrapper class for the data access object. I think a lot of things here can still be salvaged in regards to the interface naming though the parameters will be changing. This isn’t really even complete, just a mock-up of all the things I thought I would need in terms of a contextual interface for managing permissions.
<?php
/*
* Access Control List for managing user permissions
*/
$this->import('App.Core.Resource');
class MCPACL extends MCPResource {
/*
* Singleton ACL instance
*/
private static
$_objACL;
private
/*
* Access control list data access layer
*/
$_objDAOACL;
public static function createInstance(MCP $objMCP) {
if(self::$_objACL === null) {
self::$_objACL = new MCPACL($objMCP);
}
return self::$_objACL;
}
public function __construct(MCP $objMCP) {
parent::__construct($objMCP);
$this->_init();
}
private function _init() {
/*
* Get ACL DAO
*/
$this->_objDAOACL = $this->_objMCP->getInstance('App.Resource.ACL.DAO.DAOACL',array($this->_objMCP));
}
/*
* Check that a user has permission to do something
*
* @param str permission
* @retrun bool yes/no
*/
public function hasPermission($strPermission) {
return $this->_objDAOACL->hasPermission($strPermission);
}
/*
* Grant permission to user
*
* @param str permission
*/
public function addUserPermission($strPermission) {
}
/*
* Deny user the permission to do something
*
* @param str permission
*/
public function denyUserPermission($strPermission) {
}
/*
* Removes user permission
* NOTE: Not the same as deny
*
* @param str permssion
*/
public function removeUserPermission($strPermission) {
}
/*
* Remove permission from a role
*
* @param str permission
* @param str role
*/
public function removeRolePermission($strPermission,$strRole) {
}
/*
* Add permission to role
*
* @param str permission
* @param str role
*/
public function addRolePermission($strPermission,$strRole) {
}
}
?>