node access in drupal 7 (and drupal 8)
DESCRIPTION
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 8Ken Rickard is the maintainer of the Domain Access module and wrote several of the patches for Node Access in Drupal 7.TRANSCRIPT
Node Access in Drupal 7(and Drupal 8)
Ken Rickard
NYCamp 2012
Sunday, July 22, 2012
• Who the heck are you?• What is Node Access?• What changed in Drupal 7?• How do I....?• What’s up for Drupal 8?
Sunday, July 22, 2012
• Ken Rickard
• Drupal since 2004
• Domain Access
• @agentrickard
Sunday, July 22, 2012
Sunday, July 22, 2012
Drupal 7 Module Development
Sunday, July 22, 2012
AaronWinborn.com
Sunday, July 22, 2012
What is Node Access?
Sunday, July 22, 2012
Sunday, July 22, 2012
• Powerful
• Unintelligible
• Unpredictable
• Multi-dimensional
Sunday, July 22, 2012
Drupal’s system for controlling the access to nodes (content) for:
• View
• Edit
• Delete
• and now...Create
What is Node Access?
Sunday, July 22, 2012
• Create permissions!
• Alter hooks!
• ‘Bypass node access’
• Access control modules
• Node Access API modules
Drupal 7 Goodness
Sunday, July 22, 2012
Sunday, July 22, 2012
chx moshe Crell DaveCohen
someguy
Sunday, July 22, 2012
Sunday, July 22, 2012
• Filter listing queries.
• Assert access rights on CRUD.
• API for managing access rights.
Node Access is multipurpose
Sunday, July 22, 2012
Sunday, July 22, 2012
• Drupal 6: hook_db_rewrite_sql()
• Drupal 7: ‘node access’ query flag.
Sunday, July 22, 2012
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
Filter queries in Drupal 7
$result = db_select('node', 'n') ->fields('n', array('nid')) ->condition('promote', 1) ->condition('status', 1) ->orderBy('sticky', 'DESC') ->orderBy('created', 'DESC') ->addTag('node_access') ->execute();
Sunday, July 22, 2012
Failure to tag your query is a security
violation.
Sunday, July 22, 2012
Sunday, July 22, 2012
Why?
Sunday, July 22, 2012
Sunday, July 22, 2012
Sunday, July 22, 2012
Blue Red Green Grey
Our Clients
Sunday, July 22, 2012
Blue Red Green Grey
Organic Groups
Sunday, July 22, 2012
Blue RecentPosts
With proper access control
Sunday, July 22, 2012
Blue
Without proper access control
Sunday, July 22, 2012
Sunday, July 22, 2012
• 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
if (user_access(‘administer nodes’)) { return t(“I’ll be back.”);}
Boolean assertions
Sunday, July 22, 2012
Conditional access check
hook_node_grants($account, $op)
hook_node_access($node, $op, $account)
Sunday, July 22, 2012
Sunday, July 22, 2012
• Those two functions look similar.
• But they aren’t.
Sunday, July 22, 2012
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
// $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
// 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
foreach (node_access_grants($op, $account) as $realm => $gids) { foreach ($gids as $gid) { $grants->condition(db_and() ->condition('gid', $gid) ->condition('realm', $realm) ); } } if (count($grants) > 0) { $query->condition($grants); } $result = (bool) $query ->execute() ->fetchField(); $rights[$account->uid][$cid][$op] = $result; return $result; }
Sunday, July 22, 2012
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
Sunday, July 22, 2012
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
Access control modules
• Act on Create, View, Update & Delete.
• No tables*.
• No queries*.
• Just business logic.
• DENY, ALLOW, IGNORE
Sunday, July 22, 2012
The operation condition matters!
‘create’‘delete’‘update’‘view’
Sunday, July 22, 2012
Sunday, July 22, 2012
/** * 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
Hooray!
Sunday, July 22, 2012
• No more hook_menu_alter().
• Explicit DENY grants.
• Explicit ALLOW grants.
• Apply to all node types!
• Apply to node creation!
Sunday, July 22, 2012
Boo!
Sunday, July 22, 2012
• Individual nodes / actions only.
• Running lookup queries per node can be expensive.
• Can override other modules.
• Cannot generate accurate lists.
Sunday, July 22, 2012
Sunday, July 22, 2012
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
The {node_access} table.
Sunday, July 22, 2012
hook_node_access_records()
Data Entry
node_save()
node_access_acquire_grants()
{node_access}
Sunday, July 22, 2012
function example_node_access_records($node) { $grants[] = array( 'realm' => 'example_author', 'gid' => $node->uid, 'grant_view' => 1, 'grant_update' => 1, 'grant_delete' => 1, 'priority' => 0, ); return $grants; }
Sunday, July 22, 2012
hook_node_grants()
Inbound Request
$user
node_access_grants()
Page / Request Render
Sunday, July 22, 2012
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
• 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
• Two-part system!
• One query does not cover all cases!
‘create’‘delete’‘update’‘view’
Sunday, July 22, 2012
Sunday, July 22, 2012
Rebuilding the {node_access} table
Sunday, July 22, 2012
• Make sure you hook_node_load() your data.
• node_load() must match node_save()
• Other modules may depend on you!
Sunday, July 22, 2012
Devel Node Access
• Debugging tools for developers.
• And site administrators!
Sunday, July 22, 2012
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)); } break; case 'domain_site': $return .= t('Viewable on all affiliate sites.'); break; case 'domain_id': $return .= t('Viewable on %domain<br />%domain privileged editors may edit and delete', array('%domain' => $domain['subdomain'])); break; default: // This is not our grant, do not return anything. $return = NULL; break; } return $return;}
Sunday, July 22, 2012
Sunday, July 22, 2012
Add debugging to your module, too
Sunday, July 22, 2012
Sunday, July 22, 2012
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
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
hook_node_grants_alter()
• Alter the user’s grants at request time.
• Quick and easy!
Sunday, July 22, 2012
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
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
Sunday, July 22, 2012
It’s not a tumor.
Sunday, July 22, 2012
• Language-sensitive
• Abstract to all entities
• Remove hook_node_access()?
• Better list queries in core?
• Support AND and OR logic?
Drupal 8 Changes
Sunday, July 22, 2012
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
• 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
Key issues
Sunday, July 22, 2012
Let’s get to work
Sunday, July 22, 2012