This talk will look at the features and changes in the Node Access system for Drupal 7.
Out of the box, Drupal is a great system for creating and managing content. However, there are cases where your needs require additional requirements for which users can create, view, edit and delete content. To solve this problem, Drupal provides its Node Access system.
Node Access provides an API for determining the grants, or permissions, that a user has for each node. By understanding how these grants work, a module developer can create and enforce complex access rules.
We will cover some (or all) of the following topics.
- Node Access compared to user_access() and other permission checks.
- How Drupal grants node permissions.
- The node_access() function.
- hook_node_access() compared to {node_access}.
- Controlling permission to create content.
- Using hook_node_access().
- When to write a Node Access module.
- The {node_access} table and its role.
- Defining your moduleâs access rules.
- Using hook_node_access_records().
- Using hook_node_grants().
- Rebuilding the {node_access} table.
- Modifying the behavior of other modules.
- Using hook_node_access_records_alter().
- Using hook_node_grants_alter().
- Testing and debugging you module.
- Using Devel Node Access
- Roadmap for Drupal 8
Ken Rickard is the maintainer of the Domain Access module and wrote several of the patches for Node Access in Drupal 7.
10. What is Node Access?
Drupal’s system for controlling the
access to nodes (content) for:
• View
• Edit
• Delete
• and now...Create
Sunday, July 22, 2012
11. Drupal 7 Goodness
• Create permissions!
• Alter hooks!
• ‘Bypass node access’
• Access control modules
• Node Access API modules
Sunday, July 22, 2012
18. Filter queries in Drupal 6
$result = db_query(db_rewrite_sql(
“SELECT nid FROM {node} WHERE
status = 1 AND
promote = 1
ORDER BY
sticky DESC,
created DESC”
));
Sunday, July 22, 2012
30. • Node Access is not user_access().
• Node Access is an API for content
CRUD permissions.
• It extends Drupal’s core permissions
system.
• It is contextual.
Sunday, July 22, 2012
31. Boolean assertions
if (user_access(‘administer nodes’)) {
return t(“I’ll be back.”);
}
Sunday, July 22, 2012
34. • Those two functions look similar.
• But they aren’t.
Sunday, July 22, 2012
35. node_access() Walk-through
function node_access($op, $node, $account = NULL) {
$rights = &drupal_static(__FUNCTION__, array());
if (!$node || !in_array($op, array('view', 'update', 'delete', 'create'),
TRUE)) {
// If there was no node to check against, or the $op was not one of the
// supported ones, we return access denied.
return FALSE;
}
// If no user object is supplied, the access check is for the current user.
if (empty($account)) {
$account = $GLOBALS['user'];
}
Sunday, July 22, 2012
36. // $node may be either an object or a node type. Since node types cannot be
// an integer, use either nid or type as the static cache id.
$cid = is_object($node) ? $node->nid : $node;
// If we've already checked access for this node, user and op, return from
// cache.
if (isset($rights[$account->uid][$cid][$op])) {
return $rights[$account->uid][$cid][$op];
}
if (user_access('bypass node access', $account)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
if (!user_access('access content', $account)) {
$rights[$account->uid][$cid][$op] = FALSE;
return FALSE;
}
Sunday, July 22, 2012
37. // We grant access to the node if both of the following conditions are met:
// - No modules say to deny access.
// - At least one module says to grant access.
// If no module specified either allow or deny, we fall back to the
// node_access table.
$access = module_invoke_all('node_access', $node, $op, $account);
if (in_array(NODE_ACCESS_DENY, $access, TRUE)) {
$rights[$account->uid][$cid][$op] = FALSE;
return FALSE;
}
elseif (in_array(NODE_ACCESS_ALLOW, $access, TRUE)) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
// Check if authors can view their own unpublished nodes.
if ($op == 'view' && !$node->status && user_access('view own unpublished
content', $account) && $account->uid == $node->uid && $account->uid != 0) {
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
Sunday, July 22, 2012
39. elseif (is_object($node) && $op == 'view' && $node->status) {
// If no modules implement hook_node_grants(), the default behavior is to
// allow all users to view published nodes, so reflect that here.
$rights[$account->uid][$cid][$op] = TRUE;
return TRUE;
}
}
return FALSE;
}
Sunday, July 22, 2012
41. hook_node_access($node, $op, $account)
• Replaces hook_access().
• Can be run by any module!
• Can return TRUE, FALSE or NULL.
• Used for access to individual nodes.
Sunday, July 22, 2012
42. Access control modules
• Act on Create, View, Update & Delete.
• No tables*.
• No queries*.
• Just business logic.
• DENY, ALLOW, IGNORE
Sunday, July 22, 2012
45. /**
* Implement hook_node_access().
*
* Only allow posts by users more than two days old.
*/
function delay_node_access($node, $op, $account) {
if ($op != 'create') {
return NODE_ACCESS_IGNORE;
}
if (empty($account->created) ||
$account->created > (REQUEST_TIME - (48*3600)))
{
return NODE_ACCESS_DENY;
}
return NODE_ACCESS_IGNORE;
}
Sunday, July 22, 2012
47. • No more hook_menu_alter().
• Explicit DENY grants.
• Explicit ALLOW grants.
• Apply to all node types!
• Apply to node creation!
Sunday, July 22, 2012
49. • Individual nodes / actions only.
• Running lookup queries per node
can be expensive.
• Can override other modules.
• Cannot generate accurate lists.
Sunday, July 22, 2012
51. NODE ACCESS API
• Uses {node_access} for list queries.
• Creates JOINs to return lists of nodes.
• Does not act on ‘create’ operation.
• Requires numeric keys.
Sunday, July 22, 2012
56. function example_node_grants($account, $op) {
if (user_access('access private content', $account)) {
$grants['example'] = array(1);
}
$grants['example_owner'] = array($account->uid);
return $grants;
}
Sunday, July 22, 2012
57. • Map the user’s grants to the stored
grants.
• JOIN {node_access} to the query.
• Return the node or not.
• OR based access logic.
Sunday, July 22, 2012
58. • Two-part system!
• One query does not cover all cases!
‘create’
‘delete’
‘update’
‘view’
Sunday, July 22, 2012
61. • Make sure you hook_node_load()
your data.
• node_load() must match node_save()
• Other modules may depend on you!
Sunday, July 22, 2012
62. Devel Node Access
• Debugging tools for developers.
• And site administrators!
Sunday, July 22, 2012
63. hook_node_access_explain()
/**
* Implements hook_node_access_explain for devel.module
*/
function domain_node_access_explain($row) {
$_domain = domain_get_domain();
$active = $_domain['subdomain'];
$domain = domain_lookup($row->gid);
$return = t('Domain Access') . ' -- ';
switch ($row->realm) {
case 'domain_all':
if (domain_grant_all() == TRUE) {
$return .= t('True: Allows content from all domains to be
shown.');
}
else {
$return .= t('False: Only allows content from the active
domain (%domain) or from all affiliates.', array('%domain' =>
$active));
}
Sunday, July 22, 2012
67. hook_node_access_records_alter()
• Change storage rules before they are
written to the database!
• Remember to alter node storage as
needed, too!*
• * Runs after node_save() :-(
Sunday, July 22, 2012
68. function hook_node_access_records_alter(&$grants, $node) {
// Our module allows editors to mark specific articles
// with the 'is_preview' field.
if ($node->is_preview) {
// Our module grants are set in $grants['example'].
$temp = $grants['example'];
// Now remove all module grants but our own.
$grants = array('example' => $temp);
}
}
Sunday, July 22, 2012
69. hook_node_grants_alter()
• Alter the user’s grants at request
time.
• Quick and easy!
Sunday, July 22, 2012
70. function hook_node_grants_alter(&$grants, $account, $op) {
// Get our list of banned roles.
$restricted = variable_get('example_restricted_roles', array());
if ($op != 'view' && !empty($restricted)) {
foreach ($restricted as $role_id) {
if (isset($user->roles[$role_id])) {
$grants = array();
}
}
}
}
Sunday, July 22, 2012
71. hook_query_alter()
/**
* Implements hook_query_TAG_alter().
*
* If enabled, force admins to use Domain Access rules.
*/
function domain_query_node_access_alter($query) {
$admin_force = variable_get('domain_force_admin', FALSE);
// In any of the following cases, do not enforce any rules.
if (empty($admin_force) || !user_access('bypass node access')
|| domain_grant_all()) {
return;
}
domain_alter_node_query($query);
}
Sunday, July 22, 2012
74. Drupal 8 Changes
• Language-sensitive
• Abstract to all entities
• Remove hook_node_access()?
• Better list queries in core?
• Support AND and OR logic?
Sunday, July 22, 2012
75. function node_access($op, $node, $account = NULL, $langcode = NULL) {
$rights = &drupal_static(__FUNCTION__, array());
...
// If we've already checked access for this node, user and op, return from
// cache.
if (isset($rights[$account->uid][$cid][$langcode][$op])) {
return $rights[$account->uid][$cid][$langcode][$op];
}
if (user_access('bypass node access', $account)) {
$rights[$account->uid][$cid][$langcode][$op] = TRUE;
return TRUE;
}
if (!user_access('access content', $account)) {
$rights[$account->uid][$cid][$langcode][$op] = FALSE;
return FALSE;
}
Sunday, July 22, 2012
76. Key issues
• Make a general access API
• http://drupal.org/node/777578
• Make node_access() language aware
• http://drupal.org/node/1658814
• Query madness
• http://drupal.org/node/1349080
Sunday, July 22, 2012