It is my understanding that with the node access patch supplied with your module, and (I suppose) some patching to Taxonomy Access Control and Organic Group modules, we can effectively get these two modules working together on the access grants level.

That is, if a node is created that has both taxonomy and belongs to a group, a user can NOT access it unless he has grants for BOTH the taxonomy and the group. Right now, the behaviour is that a user can access the node if he has EITHER a grant for the taxonomy OR a grant for the group.

I'm writing to see if you can get me started in the right direction on this. I suppose step one is to apply the patch. But, once done, what do I need to do with TAC and OG so that they work together as I've described above?

I know you've spent a LOT of time and effort explaining this patch and how it works and why it's the best solution to this problem. I tend to agree with you, but just have not figured out specifically what needs to be done in order to do it.

Thanks for any help in this.

Comments

agentrickard’s picture

OK. The key here is that we add a 'groups' element to the array returned by hook_node_grants().

For example, OG normally returns the following in response to that hook:

function og_node_grants($account, $op) {
  if (variable_get('og_enabled', FALSE)) {
    if ($op == 'view') {
      $grants['og_public'][] = 0; // everyone can see a public node
    }

    // get subscriptions
    if ($subscriptions = og_get_subscriptions($account->uid)) {
      foreach ($subscriptions as $key => $val) {
        if ($op == 'view') {
          $grants['og_subscriber'][] = $key;
        }
        if (($op == 'update' || $op == 'delete') && $val['is_admin']) {
          $grants['og_subscriber'][] = $key;
        }
      }
    }
    return $grants ? $grants : array();
  }
}

What happens with the patch enabled is that all elements returned by hook_node_grants() that do not have a declared 'group' will by placed into the default 'all' group. This is the behavior as it stands now. All grants assigned to a common group will be placed within the same clause and chained together using OR logic.

To enable AND logic for TAC, you would simply do the following:

/**
 * Implementation of hook_node_grants()
 * Gives access to taxonomies based on the taxonomy_access table
 */
function taxonomy_access_node_grants($user, $op) {
  return array('term_access' => array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user')),
  'group' => 'tac');
}

This will place the TAC logic in a separate group, allowing the AND grants to function.

Doing so will give you:

IF (all OR og) AND (tac)

Instead of:

IF (all OR og OR tac)

If you look at the logic of http://therickards.com/api/function/domain_node_grants/Domain, you will see that there is an administrative setting to control the AND/OR behavior of DA. This is recommended.

Since DA actually writes two grants, its logic ends up like so:

IF (all OR og) AND (domain_site OR domain_id)

somebodysysop’s picture

Great. Thanks for the quick response. I think I get this. But, one dumb question: Do I need the DA module installed for this to work, or only the patch?

That is, if all I want is this node grant functionality: IF (all OR og) AND (tac)
Can I just install the patch and modify TAC_node_grants? Or, do I need to install DA and do some configurations, even if I don't need domain access?

agentrickard’s picture

Just the patch. DA is not required.

However, if you add the 'group' element to the array _without_ the patch, it will throw an error. My advice is to wrap that group logic in a settings check.

For DA, I do two things:

-- Set the variable default to FALSE == do not include a 'group' element.
-- Check on the configuration page if multiple node_access modules are installed.
---- IF the above is TRUE, allow the AND logic to be enabled.

Then when I run hook_node_grants, I check the variable before setting the 'group' element.

agentrickard’s picture

See also http://drupal.org/node/241189 for a note about OG permissions and this patch. The problem would occur with TAC as well, since OG does not write its grants in some cases.

somebodysysop’s picture

OK. started with relatively clean 5.7 site.

Installed latest stable 5.x versions of OG and Taxonomy_Access.

Verified that OG and TAC were working as expected. Created a vocabulary term and assigned it to a node within a group. As expected, users can access node if they belong to role permitted by TAC, in spite of not belonging to group.

Downloaded latest Domain Access module.

Applied multiple_node_access.patch to node.module.

Modified TAC as described above:

/**
* Implementation of hook_node_grants()
* Gives access to taxonomies based on the taxonomy_access table
*/
function taxonomy_access_node_grants($user, $op) {
  return array('term_access' => array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user')),
  'group' => 'tac');
}

Went to Admin->Content Management->Post Settings->Rebuild Permissions

On attempting to access the node which I assigned a term, get this error:

Invalid argument supplied for foreach() in /var/www/html/websites/drupal/test01/modules/node/node.module on line 2887.
agentrickard’s picture

Take that one-line IF statement out so the code is easier to read, please.

You should end up returning something like so:

$grants['term_access'] => array($user->roles); // product of the IF statement
$grants['term_access']['group'] => 'tac';

The 'group' is a keyed element in the main grants array.

somebodysysop’s picture

Sorry, but I'm trying to get up to speed on this whole node grants thing as I go. Is this ok?

function taxonomy_access_node_grants($user, $op) {
  $grants['term_access'] = array($user->roles); // product of the IF statement
  $grants['term_access']['group'] = 'tac';
  return $grants;
}

With this, I no longer get the error, but...

I created a group. I created a node in the group and made it private to the group. I then created a vocabulary term and using TAC, made it accessible only if a user belongs to a particular role. I then gave the node in the group this term.

So, theoretically, a user who belongs to the group should NOT be able to see the node unless he belongs to the correct role. This should be the (all or og) AND (tac) logic.

What is happening is that a user who belongs to the group CAN see the node.

Any ideas?

somebodysysop’s picture

I'll try to explain this more in detail to see if it helps.

I created a group called "Test Group 02".
I created a vocabulary called "Test Access".
I created a role called "GroupMember".

Created a new Test Access term: "Group Member Access". Went to Admin->User Management->Taxonomy Access: Permissions and made sure that only GroupMember role has access to Group Member Access term. Made sure that "anonymous" and "authenticated" roles do NOT have access to Group Member Access term.

Went back to Test Group 02 and created a new node: Group Member Story. This node is:

* Private to Test Group 02 (not "Public")
* Assigned the Group Member Access term.

The behaviour that we want is that a user who is a member of Test Group 02 but does NOT have the GroupMember role should NOT see the Group Member Story (TAC and OG working together).

What is actually happening is that the user who is a member of Test Group 02 CAN see the Group Member Story node.

This is what is in my node_access table for Group Member Story (NodeID = 7):

nid    rid       realm             view  update delete
7      4         og_subscriber     1     1      1
7      3         term_access       1     0      0 

I was assuming that I would get the AND logic with respect to the multiple_node_patch and therefore the user without the term_access grant would be denied access. As it stands, this user is granted access.

agentrickard’s picture

The easiest way to debug this is to install the Devel module. Turn on Devel Node Access and query logging.

You should see some node_access information and queries. We can use those to debug.

I'm on the road right now, or I would test this as well. I appreciate that you are keeping with it.

Remember, too, that users with 'administer nodes' are exempt from node access rules.

somebodysysop’s picture

StatusFileSize
new38.87 KB

OK, installed devel module. As authenticated non-admin user who does NOT have GroupMember role but belongs to Test Group 02, I accessed the Group Member Story node.

nid 4 = Test Group 02
nid 7 = Group Member Story node (that should not be seen)
rid (roleID) 3 = GroupMember (role required to see Group Member Story)

Test Access vocabulary ID = 1
Group Member Access term ID = 2

The node_access queries:

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4)) AND grant_view >= 1

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0)) AND grant_update >= 1

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0)) AND grant_delete >= 1

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 4) AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4)) AND grant_view >= 1

Don't see "term_access" anywhere except here:

taxonomy_access_db_rewrite_sql	SELECT t.tid, d.vid, BIT_OR(t.grant_list) AS grant_list FROM term_access t INNER JOIN term_data d ON t.tid = d.tid WHERE t.rid IN ('2','4') AND grant_list = 1 GROUP BY t.tid, d.vid

Attached is screenshot of node_access entries.

Hope this helps.

agentrickard’s picture

The queries above suggest that TAC is not returning any grants or that the multiple_node_access patch is not installed.

If taxonomy_access is using its own db_rewrite_sql() hook, then the patch will not have any effect.

somebodysysop’s picture

If taxonomy_access is using its own db_rewrite_sql() hook, then the patch will not have any effect.

With respect to listing, this is true. But, with respect to "View", this would be handled by node_access.

I applied the patch to node.module version 5.7. Does this look correct?

// $Id: node.module,v 1.776.2.22 2008/01/07 01:31:26 drumm Exp $

  // If the module did not override the access rights, use those set in the
  // node_access table.
  if ($op != 'create' && $node->nid) {
    $grants_sql = node_access_grants_sql($op, NULL, $user->uid, $node->status);
    // If the return value is FALSE, then the node status is unpublished and
    // none of the grants requested an access check be run.  In which case,
    // we should fall through to the final 'if' statement.
    if ($grants_sql !== FALSE) {
      if (!empty($grants_sql)) {
        $grants_sql .= ' AND';
      }  
      $sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) $grants_sql grant_$op >= 1";
      $result = db_query($sql, $node->nid);
      return (db_result($result));
    }
  }

  // Let authors view their own nodes.
  if ($op == 'view' && $user->uid == $node->uid && $user->uid != 0) {
    return TRUE;
  }

  return FALSE;
}

/**
 * Generate an SQL join clause for use in fetching a node listing.
 *
 * @param $node_alias
 *   If the node table has been given an SQL alias other than the default
 *   "n", that must be passed here.
 * @param $node_access_alias
 *   If the node_access table has been given an SQL alias other than the default
 *   "na", that must be passed here.
 * @return
 *   An SQL join clause.
 */
function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
  if (user_access('administer nodes')) {
    return '';
  }

  return 'INNER JOIN {node_access} '. $node_access_alias .' ON '. $node_access_alias .'.nid = '. $node_alias .'.nid';
}

/**
 * Generate an SQL where clause for use in fetching a node listing.
 *
 * @param $op
 *   The operation that must be allowed to return a node.
 * @param $node_access_alias
 *   If the node_access table has been given an SQL alias other than the default
 *   "na", that must be passed here.
 * @return
 *   An SQL where clause.
 */
function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $uid = NULL) {
  if (user_access('administer nodes')) {
    return;
  }

  $grants_sql = node_access_grants_sql('view', $node_access_alias, $uid);

  $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
  return $sql;
}

/**
 * Fetch an array of permission IDs granted to the given user ID.
 *
 * The implementation here provides only the universal "all" grant. A node
 * access module should implement hook_node_grants() to provide a grant
 * list for the user.
 *
 * @param $op
 *   The operation that the user is trying to perform.
 * @param $uid
 *   The user ID performing the operation. If omitted, the current user is used.
 * @return
 *   An associative array in which the keys are realms, and the values are
 *   arrays of grants for those realms.
 */
function node_access_grants($op, $uid = NULL) {
  global $user;

  if (isset($uid)) {
    $user_object = user_load(array('uid' => $uid));
  }
  else {
    $user_object = $user;
  }

  return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $user_object, $op));
}

/**
 * Determine whether the user has a global viewing grant for all nodes.
 */
function node_access_view_all_nodes() {
  static $access;

  if (!isset($access)) {
    $grants_sql = node_access_grants_sql('view');
    $sql = "SELECT COUNT(*) FROM {node_access} WHERE nid = 0 $grants_sql AND grant_view >= 1";
    $result = db_query($sql);
    $access = db_result($result);
  }

  return $access;
}

/**
 * Check the logic of node grants and prepare the sql statement.
 *
 * @param $op
 *   The operation that the user is trying to perform.
 * @param $uid
 *   The user ID performing the operation. If omitted, the current user is used.
 * @param $status
 *   The publication status of the node being checked.  Typically, node_access
 *   checks are not run for unpublished nodes.  However, some advanced uses
 *   require that users can act on unpublished nodes.
 * @return
 *   There are three possible return values.
 *   - A sql string containing the appropriate WHERE clauses, grouped together
 *   according to the logic of hook_node_grants.
 *   - An empty string, indicating that no extra rules are needed.
 *   - Boolean FALSE, indicating that all nodes are unpublished and no module
 *   has explicitly requested an access check.
 */
function node_access_grants_sql($op = 'view', $node_access_alias = NULL, $uid = NULL, $status = TRUE) {
  // Define the grants array.
  $grants = array();
  // First, acquire all the grants as an array of rules.
  $rules = node_access_grants($op, $uid);

  // Prepare the table alias, if needed.
  if (!empty($node_access_alias)) {
    $alias = $node_access_alias .'.';
  }
  // It may be that only the default rule is active.  In that case, we can skip the
  // process below.
  if (count($rules) == 1 && key($rules) == 'all') {
    $grants_sql .= "AND (". $alias ."realm = 'all' AND ". $alias ."gid = 0)";    
  }
  else {
    // Process the grants into logical groupings.
    foreach ($rules as $realm => $rule) {
      // Set the status flag.
      $status += $rule['check'];  
      // Set the default clause group.  Then check for options.
      $group = 'all';
      if (!empty($rule['group'])) {
        $group = $rule['group'];
      }
      foreach ($rule as $gid) {
        // Grants ids must be numeric.
        if (is_numeric($gid)) {
          $grants[$group][$realm] = $gid;
        }  
      }  
    }
    // In most cases, node status must be set to TRUE.  However, there are use-cases where 
    // we allow access to unpublished nodes.  So we check the status to see if we need to 
    // continue with the logic or end this IF statement.  
    if (!$status) {
      return FALSE;
    }
    $grants_sql = '';
    // Run throught the $grants, if needed and generate the query SQL.
    if (count($grants)) {
      foreach ($grants as $group => $grant) {
        $clauses = array();
        foreach ($grant as $key => $value) {
          $clauses[] = "(". $alias ."realm = '$key' AND ". $alias ."gid = $value)";
        }
        if ($group == 'all') {
          $grants_sql .= 'AND ('. implode(' OR ', $clauses) .') ';            
        }
        else {
          // Required elements must go into a subquery.  Will not work prior to MySQL 4.1.x.
          $subquery = "AND ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE ";
          $grants_sql .= $subquery .'('. implode(' OR ', $clauses) .')) ';            
        }
      }  
    }
  }  
  return $grants_sql;
}
agentrickard’s picture

Yes. That is the correct code after the patch. Let me take a look at the code.

agentrickard’s picture

Try this:

/**
 * Implementation of hook_node_grants()
 * Gives access to taxonomies based on the taxonomy_access table
 */
function taxonomy_access_node_grants($user, $op) {
  $grants['term_access'] = array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user'));
  $grants['term_access']['group'] = 'tac';
  return $grants;
}

Returns the following queries for an anonymous user:

SELECT COUNT(*) FROM node n INNER JOIN node_access na ON na.nid = n.nid WHERE (na.grant_view >= 1 AND ((na.realm = 'all' AND na.gid = 0)) AND na.nid IN (SELECT na.nid FROM node_access na WHERE ((na.realm = 'term_access' AND na.gid = 1))) ) AND ( n.promote = 1 AND n.status = 1 )
somebodysysop’s picture

I think we're really close. I believe there may be a slight problem with your multinode_access_patch code when it comes to TAC. Perhaps you may know how to fix it, if in fact, what I am suggesting is a problem.

As I explain in the following post, I have been able to achieve most of the behaviour I'm seeking. I ran into a problem, however: In Test Group 02, I created a node that I assigned the Group Member Access term. I have a user who is a member of Test Group 02 and has the GroupMember role, which means he should be able to see this node. He cannot.

As stated earlier, this is what is in my node_access table for Group Member Story (NodeID = 7):

nid    rid       realm             view  update delete
7      4         og_subscriber     1     1      1
7      3         term_access       1     0      0 

The user attempting to access this node has RoleIDs (rid) 3 and 4, in addition to 2 (authenticated) The node_access query I see for this user when trying to access this node (7) is:

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4)) AND nid IN (SELECT nid FROM node_access WHERE ((realm = 'term_access' AND gid = 4))) AND grant_view >= 1

This results in 0.

What it should say is:

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4)) AND nid IN (SELECT nid FROM node_access WHERE ((realm = 'term_access' AND gid = 4) OR (realm = 'term_access' AND gid = 3) OR (realm = 'term_access' AND gid = 2))) AND grant_view >= 1

This results in 1, which would be correct.

This user has 3 roles (2 = authenticated, 3 = GroupMember and 4 = Devel). However, it seems (in the case of TAC) that the multinode_access_patch is consistently creating a query for just the last role created.

What do you think?

somebodysysop’s picture

OK, I worked some more on this. Let me say, first off, that your code IS working for the most part. There is a problem I think we can fix, and I re-edited my previous post to explain it.

However, for the benefit of those who follow behind us on this, there some some VERY important things you should know.

The following assumes a site where a) TAC and OG are both in use; b) there are nodes created which belong to groups but are "public" (i.e., viewable by anonymous users); c) there are nodes created which do have taxonomy restrictions; d) the taxonomy restrictions are to be respected even when the node belongs to a group (i.e., users must have both the appropriate TAC AND OG access grants to view such nodes).

1. In Taxonomy Access: Permissions, you must set "Uncategorized nodes" to "View" for EVERY role you created.

Normally, if you set "Uncategorized nodes" to "View" for just anonymous and authenticated users, that does the trick. But, with the way the multinode_access_patch works, it is now necessary to make this setting for EVERY role on your site.

I created a role on my site called Devel so that my test user could see the results from the devel.module. Even though in TAC: Permissions I set anonymous and authenticated users to be able to view "Uncategorized nodes" this user, surprisingly, could not see any nodes. When I went back to TAC: Permissions and allowed the Devel role to see "Uncategorized nodes", my test user was now able to see what he was supposed to see -- exceot for Public access stuff, which is explained next.

2. Once the "Uncategorized nodes" are set to "View" for ALL nodes, your anonymous and authenticated users can now see OG content that is flagged "Public". However, what about TAC content that you wish to be publically available for viewing?

If you create a vocabulary for a node that has a term that should be available to ALL users, you must go into TAC: Permissions and set that term for "View" and "List" for EVERY role on your site.

Yes, I understand that you should only have to set Public access terms in the anonymous and authenticated roles. What I'm telling you is that to get the expected behaviour (everyone can see the node) then you must basically set it public for EVERY role.

somebodysysop’s picture

I have verified that either because of the way the patch is coded, or the way taxonomy_access_node_grants is coded, node_access is NOT receiving all the roleIDs of a user.

function taxonomy_access_node_grants($user, $op) {
  $grants['term_access'] = array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user'));
  $grants['term_access']['group'] = 'tac';
  return $grants;
}

My test user had two roles outside of authenticated: RoleID 3 (GroupMember) and RoleID 4 (Devel). Every time the test user tries to access a node that he should have access to because of RoleID 3, he is denied access. In the node_access query I see: (realm = 'term_access' AND gid = 4).

I removed RoleID 4 from the user and Voila, he can now see the node. In the node_access query I see: (realm = 'term_access' AND gid = 3).

So, how do we get this code to recognize ALL the RoleIDs a user has?

agentrickard’s picture

The line that causes the problem is this one:

  $grants['term_access'] = array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user'));

I have trouble reading that syntax. I believe that what Drupal's hook_node_access_records is expecting is:

<?php
$grants['term_access'][] == // role 1;
$grants['term_access'][] == // role 2;
$grants['term_access'][] == // role 3;
$grants['term_access']['group'] == 'tac';
?

somebodysysop’s picture

OK, I created a little test. I made this change:

function taxonomy_access_node_grants($user, $op) {
//  $grants['term_access'] = array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user'));
  $grants['term_access'][] = 1;
  $grants['term_access'][] = 2;
  $grants['term_access'][] = 3;
  $grants['term_access'][] = 4;
  $grants['term_access'][] = 5; // Note I don't actually have a roleID 5, just putting this here to see what happens.
  $grants['term_access']['group'] = 'tac';
  return $grants;
}

Then created this test code to see what is actually returned:

global $user;
$grants = taxonomy_access_node_grants($user, $op);
print_r($grants);

This is what I got back:

 Array ( [term_access] => Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [group] => tac ) )

When I attempted to access the node in question (still using this this test taxonomy_access_node_grants()), I got access denied. Predictably, the node access query:

SELECT COUNT(*) FROM node_access WHERE (nid = 0 OR nid = 7) AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4)) AND nid IN (SELECT nid FROM node_access WHERE ((realm = 'term_access' AND gid = 5))) AND grant_view >= 1

Note that roleID 5 is the last roleID in the array.

I went back to the original taxonomy_access_node_grants() to see what it was returning:

Array ( [term_access] => Array ( [0] => 2 [1] => 3 [2] => 4 ) )

I then changed taxonomy_access_node_grants() back to the code I've been using for the multinode_access_patch:

Array ( [term_access] => Array ( [0] => 2 [1] => 3 [2] => 4 [group] => tac ) )

This is is using the line you say is causing the problem:

$grants['term_access'] = array_keys(is_array($user->roles) ? $user->roles : array(1 => 'anonymous user'));

If this is NOT what hook_node_grants is supposed to return, then we need to know what it should return.

If this IS what hook_node_grants is supposed to return, then I think we need to look at how the multinode_access_patch is processing this array. I've been trying to figure that out, but just don't know enough about your code and access grants in general (apparently) to tell.

Does this help any? It looks like either the multinode_access_patch code needs to be changed to identify multiple rids from taxonomy_access, or the taxonomy_access_node_grants code needs to be changed so that the patch processes it correctly. One or the other, looks like we're thisclose to making it work.

agentrickard’s picture

Yes. This helps. I just need to look at what happens when DA returns multiple domain_id grants.

With the patch, node_access_grants_sql() should split these into separate OR clauses within the subselect.

Look at the code after the lines:

 // Run throught the $grants, if needed and generate the query SQL.
somebodysysop’s picture

OK, looked even more deeply. Our problem is here:

      foreach ($rule as $gid) {
        // Grants ids must be numeric.
        if (is_numeric($gid)) {
          print "<br>" . $gid;
          $grants[$group][$realm] = $gid;
        }  
      }  

The $rules value contains all the taxonomy access rids for the user.

Array ( [all] => Array ( [0] => 0 ) [og_public] => Array ( [0] => 0 ) [og_subscriber] => Array ( [0] => 4 ) [term_access] => Array ( [0] => 2 [1] => 3 [2] => 4 ) )

However, the resulting $grants value contains only the last one.

Array ( [all] => Array ( [all] => 0 [og_public] => 0 [og_subscriber] => 4 [term_access] => 4 ) )

I actually printed out the $gid as the above code executed, and the TAC rids are there, but only the last one is inserted into the $grants table, and thus, the $grants_sql:

AND ((realm = 'all' AND gid = 0) OR (realm = 'og_public' AND gid = 0) OR (realm = 'og_subscriber' AND gid = 4) OR (realm = 'term_access' AND gid = 4))

That makes sense since in this particular foreach clause, $grants[$group][$realm] NEVER changes. Again, it is my lack of knowledge about the grant table that prevents me from determing how this should be changed, but hopefully you can see it immediately.

somebodysysop’s picture

By Jove, I think I've got it. Below is test code I created to simulate node_access_grants_sql().

I basically added an additional array element to $grants array so that multiple gids for the same realm can be accomodated. Please check it out. Thanks!


  global $user;
  $op = 'view';
  $uid = $user->uid;
  $status = TRUE;

  // Define the grants array.
  $grants = array();
  // First, acquire all the grants as an array of rules.
  $rules = node_access_grants($op, $uid);
    print "<br>rules<br>";
	print_r($rules);
	print "<hr>";

  // Prepare the table alias, if needed.
  if (!empty($node_access_alias)) {
    $alias = $node_access_alias .'.';
  }
  // It may be that only the default rule is active.  In that case, we can skip the
  // process below.
  if (count($rules) == 1 && key($rules) == 'all') {
    $grants_sql .= "AND (". $alias ."realm = 'all' AND ". $alias ."gid = 0)";    
  }
  else {
    // Process the grants into logical groupings.
    foreach ($rules as $realm => $rule) {
      // Set the status flag.
      $status += $rule['check'];  
      // Set the default clause group.  Then check for options.
      $group = 'all';
      if (!empty($rule['group'])) {
        $group = $rule['group'];
      }
      foreach ($rule as $gid) {
        // Grants ids must be numeric.
        if (is_numeric($gid)) {
          print "<br>group=" . $group . " realm=" . $realm . " gid=" . $gid;
/*********************************
 * Change here
 * Added additional array element to $grants
 *********************************/
          $grants[$group][$realm][] = $gid;
        }  
      }  
    }
    // In most cases, node status must be set to TRUE.  However, there are use-cases where 
    // we allow access to unpublished nodes.  So we check the status to see if we need to 
    // continue with the logic or end this IF statement.  
    if (!$status) {
      print "<br>FALSE";
    }
    print "<br>grants<br>";
	print_r($grants);
	print "<hr>";
    $grants_sql = '';
    // Run throught the $grants, if needed and generate the query SQL.
    if (count($grants)) {
      foreach ($grants as $group => $grant) {
        $clauses = array();
        foreach ($grant as $key => $value) {
/*****************************************
 * Change here
 * Code to process additional array element to $grants
 *****************************************/
          foreach ($value as $key0 => $value0) {
            $clauses[] = "(". $alias ."realm = '$key' AND ". $alias ."gid = $value0)";
          }
        }
        if ($group == 'all') {
          $grants_sql .= 'AND ('. implode(' OR ', $clauses) .') ';            
        }
        else {
          // Required elements must go into a subquery.  Will not work prior to MySQL 4.1.x.
          $subquery = "AND ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE ";
          $grants_sql .= $subquery .'('. implode(' OR ', $clauses) .')) ';            
        }
      }  
    }
  }  

  print "<br>";
  print $grants_sql;

I have modified my node_access_grants_sql() with the changes identified above and it appears to be working as I would anticipate: Allowing TAC and OG to work together in concert.

agentrickard’s picture

I see what you're doing. I'm really going to need to spend some time digging into this code snippet, probably this weekend.

somebodysysop’s picture

OK. Here is the code change that I'm actually testing now. So far, so good. It appears to be working for the scenarios I have earlier described. Continuing to test and will report results.

If you could test to make sure this doesn't adversely affect multinode_access with DA, that would be great.

/**
 * Check the logic of node grants and prepare the sql statement.
 *
 * @param $op
 *   The operation that the user is trying to perform.
 * @param $uid
 *   The user ID performing the operation. If omitted, the current user is used.
 * @param $status
 *   The publication status of the node being checked.  Typically, node_access
 *   checks are not run for unpublished nodes.  However, some advanced uses
 *   require that users can act on unpublished nodes.
 * @return
 *   There are three possible return values.
 *   - A sql string containing the appropriate WHERE clauses, grouped together
 *   according to the logic of hook_node_grants.
 *   - An empty string, indicating that no extra rules are needed.
 *   - Boolean FALSE, indicating that all nodes are unpublished and no module
 *   has explicitly requested an access check.
 */
function node_access_grants_sql($op = 'view', $node_access_alias = NULL, $uid = NULL, $status = TRUE) {
  // Define the grants array.
  $grants = array();
  // First, acquire all the grants as an array of rules.
  $rules = node_access_grants($op, $uid);
  // Prepare the table alias, if needed.
  if (!empty($node_access_alias)) {
    $alias = $node_access_alias .'.';
  }
  // It may be that only the default rule is active.  In that case, we can skip the
  // process below.
  if (count($rules) == 1 && key($rules) == 'all') {
    $grants_sql .= "AND (". $alias ."realm = 'all' AND ". $alias ."gid = 0)";    
  }
  else {
    // Process the grants into logical groupings.
    foreach ($rules as $realm => $rule) {
      // Set the status flag.
      $status += $rule['check'];  
      // Set the default clause group.  Then check for options.
      $group = 'all';
      if (!empty($rule['group'])) {
        $group = $rule['group'];
      }
      foreach ($rule as $gid) {
        // Grants ids must be numeric.
        if (is_numeric($gid)) {
          $grants[$group][$realm][] = $gid;
        }  
      }  
    }
    // In most cases, node status must be set to TRUE.  However, there are use-cases where 
    // we allow access to unpublished nodes.  So we check the status to see if we need to 
    // continue with the logic or end this IF statement.  
    if (!$status) {
      return FALSE;
    }
    $grants_sql = '';
    // Run throught the $grants, if needed and generate the query SQL.
    if (count($grants)) {
      foreach ($grants as $group => $grant) {
        $clauses = array();
        foreach ($grant as $key => $value) {
          foreach ($value as $key0 => $value0) {
            $clauses[] = "(". $alias ."realm = '$key' AND ". $alias ."gid = $value0)";
          }
        }
        if ($group == 'all') {
          $grants_sql .= 'AND ('. implode(' OR ', $clauses) .') ';            
        }
        else {
          // Required elements must go into a subquery.  Will not work prior to MySQL 4.1.x.
          $subquery = "AND ". $alias ."nid IN (SELECT ". $alias ."nid FROM {node_access} ". $node_access_alias ." WHERE ";
          $grants_sql .= $subquery .'('. implode(' OR ', $clauses) .')) ';            
        }
      }  
    }
  }  
  return $grants_sql;
}
agentrickard’s picture

Status: Active » Needs review
StatusFileSize
new6.22 KB

Man, credit where credit is due. That was a nice catch. In the DA world, we never pass an array of grants for a single realm, so I never tested that case.

Take a look and see if this behaves as expected.

somebodysysop’s picture

Thanks for the very prompt responses. Applied patch. Appears to be working. Doing some tests. Will report back.

Again, thanks for the help in figuring this one out. I've been trying to get an elegant solution to TAC/OG cooperation for over a year, and this just might finally do it.

agentrickard’s picture

Be sure to note that over at http://drupal.org/node/196922

Also be aware of http://drupal.org/node/241189 -- with this patch, it becomes essential that all node access modules write access rules for all nodes.

agentrickard’s picture

Status: Needs review » Fixed

Added to 5.x.1.4 and 6.x.1.0-beta2

Anonymous’s picture

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.