Node Access in Drupal 7 (and Drupal 8)

9,846 views
9,646 views

Published on

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.

Published in: Technology, Business

Node Access in Drupal 7 (and Drupal 8)

  1. 1. Node Access in Drupal 7 (and Drupal 8) Ken Rickard NYCamp 2012Sunday, July 22, 2012
  2. 2. • 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
  3. 3. • Ken Rickard • Drupal since 2004 • Domain Access • rickard@Palantir.net • @agentrickardSunday, July 22, 2012
  4. 4. Sunday, July 22, 2012
  5. 5. Drupal 7 Module DevelopmentSunday, July 22, 2012
  6. 6. AaronWinborn.comSunday, July 22, 2012
  7. 7. What is Node Access?Sunday, July 22, 2012
  8. 8. Sunday, July 22, 2012
  9. 9. • Powerful • Unintelligible • Unpredictable • Multi-dimensionalSunday, July 22, 2012
  10. 10. What is Node Access? Drupal’s system for controlling the access to nodes (content) for: • View • Edit • Delete • and now...CreateSunday, July 22, 2012
  11. 11. Drupal 7 Goodness • Create permissions! • Alter hooks! • ‘Bypass node access’ • Access control modules • Node Access API modulesSunday, July 22, 2012
  12. 12. Sunday, July 22, 2012
  13. 13. chx moshe Crell Dave some Cohen guySunday, July 22, 2012
  14. 14. Sunday, July 22, 2012
  15. 15. Node Access is multipurpose • Filter listing queries. • Assert access rights on CRUD. • API for managing access rights.Sunday, July 22, 2012
  16. 16. Sunday, July 22, 2012
  17. 17. • Drupal 6: hook_db_rewrite_sql() • Drupal 7: ‘node access’ query flag.Sunday, July 22, 2012
  18. 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
  19. 19. 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
  20. 20. Failure to tag your query is a security violation.Sunday, July 22, 2012
  21. 21. Sunday, July 22, 2012
  22. 22. Why?Sunday, July 22, 2012
  23. 23. Sunday, July 22, 2012
  24. 24. Sunday, July 22, 2012
  25. 25. Blue Red Green Grey Our ClientsSunday, July 22, 2012
  26. 26. Blue Red Green Grey Organic GroupsSunday, July 22, 2012
  27. 27. With proper access control Recent Blue PostsSunday, July 22, 2012
  28. 28. Without proper access control BlueSunday, July 22, 2012
  29. 29. Sunday, July 22, 2012
  30. 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. 31. Boolean assertions if (user_access(‘administer nodes’)) { return t(“I’ll be back.”); }Sunday, July 22, 2012
  32. 32. Conditional access check hook_node_grants($account, $op) hook_node_access($node, $op, $account)Sunday, July 22, 2012
  33. 33. Sunday, July 22, 2012
  34. 34. • Those two functions look similar. • But they aren’t.Sunday, July 22, 2012
  35. 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. 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 weve 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. 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
  38. 38. 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
  39. 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
  40. 40. Sunday, July 22, 2012
  41. 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. 42. Access control modules • Act on Create, View, Update & Delete. • No tables*. • No queries*. • Just business logic. • DENY, ALLOW, IGNORESunday, July 22, 2012
  43. 43. The operation condition matters! ‘create’ ‘delete’ ‘update’ ‘view’Sunday, July 22, 2012
  44. 44. Sunday, July 22, 2012
  45. 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
  46. 46. Hooray!Sunday, July 22, 2012
  47. 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
  48. 48. Boo!Sunday, July 22, 2012
  49. 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
  50. 50. Sunday, July 22, 2012
  51. 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
  52. 52. The {node_access} table.Sunday, July 22, 2012
  53. 53. hook_node_access_records() Data Entry node_save() node_access_acquire_grants() {node_access}Sunday, July 22, 2012
  54. 54. 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
  55. 55. hook_node_grants() Inbound Request $user node_access_grants() Page / Request RenderSunday, July 22, 2012
  56. 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. 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. 58. • Two-part system! • One query does not cover all cases! ‘create’ ‘delete’ ‘update’ ‘view’Sunday, July 22, 2012
  59. 59. Sunday, July 22, 2012
  60. 60. Rebuilding the {node_access} tableSunday, July 22, 2012
  61. 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. 62. Devel Node Access • Debugging tools for developers. • And site administrators!Sunday, July 22, 2012
  63. 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
  64. 64. Sunday, July 22, 2012
  65. 65. Add debugging to your module, tooSunday, July 22, 2012
  66. 66. Sunday, July 22, 2012
  67. 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. 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. 69. hook_node_grants_alter() • Alter the user’s grants at request time. • Quick and easy!Sunday, July 22, 2012
  70. 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. 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
  72. 72. Sunday, July 22, 2012
  73. 73. It’s not a tumor.Sunday, July 22, 2012
  74. 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. 75. function node_access($op, $node, $account = NULL, $langcode = NULL) { $rights = &drupal_static(__FUNCTION__, array()); ... // If weve 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. 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/1349080Sunday, July 22, 2012
  77. 77. Let’s get to workSunday, July 22, 2012
  78. 78. • THANK YOU! • Ken Rickard • rickard@Palantir.net • @agentrickardSunday, July 22, 2012

×