On a setup with both taxonomy_access and view_unpublished enabled it is not possible to control permissions from taxonomy_access anymore.

For example I got a UNpublished node that is tagged with term A which is mapped to role B and set to ALLLOW UPDATE in TAC.

In the permissions settings I granted role B "view any unpublished content" (view_unpublished module).

So any user within role B can see that node but that doesn't include editing which should be also possible because of the settings in TAC.

Comments

xjm’s picture

Oh, you must be with the guys in Austria. :)

I'm not sure what might have changed since Drupal 6 in this regard. However, my first thought is to do away with view_unpublished, and instead replace core's published/unpublished functionality with a term reference field:

  1. You'd have a vocabulary, something like Workflow state.
  2. That vocabulary would include only the term Unpublished. (Though you could add more if desired!)
  3. You would add a term reference field to the appropriate content types using the Workflow state vocabulary and the checkboxes field widget. (Edit: If needed, you could customize the node form to make it look the same to the end user with a hook_form_alter() or the like.)
  4. Then, you could integrate the functionality of granting access to unpublished nodes into TAC's permissions schemes:
    • You could set the anonymous and authenticated users' grants for Unpublished to Deny.
    • For roles that should have access to view all unpublished nodes, and update only some, you would set the grants for Unpublished to View = Allow, Update = Ignore, Delete = Ignore (or Deny if they should never delete them).
    • Then, you would add additional grants for the appropriate terms in the other vocabularies for those roles.

So, in your example, Role B would have Allow/Ignore/Ignore for Unpublished, and Allow/Allow/Allow (if deletion is allowed) for Term A.

xjm’s picture

Oh, forgot to add, your administrative roles (whichever ones are setting nodes to unpublished in the first place) would need the Create grant for the Worfklow state vocabulary default, but normal authenticated users would not. No roles should have the List for this term.

sebastian.haas’s picture

Yep! :)

Thank you very much for your time and efforts, I appreciate that. I also thought about doing a solution view_unpublished independently which actually seems to be the much more painless approach than fixing that issue as well as it would not provide really _new_ functionality-

However, I have to double check that with my chief since it's not me who has to live with that setup and it's charactaristics for the next few years. :)

I'm setting status to postponed until I have an direction to go- Maybe I find some time to dive into that subject and get an idea of how much work would have to be spend on that module integration.

sebastian.haas’s picture

Status: Active » Postponed

status changed

sebastian.haas’s picture

The reason for using view_unpublished in our case is http://drupal.org/project/scheduler which depends on the drupal core field published/unpublished.

So I have to reopen my request and will try to get more information about what is causing that issue.

sebastian.haas’s picture

Status: Postponed » Active

reopened request

xjm’s picture

Ahh, that makes sense. Yes, a simple workflow vocabulary will not work in this case.

Be sure to take a look at http://drupal.org/node/765832 (which is based on LTE Drupal 6 but is mostly still applicable). We might want to make a little helper module with a hook_node_grants_alter().

sebastian.haas’s picture

Today I found some time to take a look on the subject. It think we should definitely think about a better integration of the published/unpublished state of nodes since its a core feature...

For example: If I have the authenticated users (which is tac's default setting) and also anonymouse users role set to View=Allow, this completely removes the published/unpublished functionality because of the following to lines, inserted into table node_access:

11 1 term_access 1 0 0
11 2 term_access 1 0 0

where 11 is my unpublished node's id and gids 1 and 2 are standing for anonymous users and authenticated users.

So besides of the fact whether a node is published or not it will become accesible through tac. So maybe it should be possible to define different behaviour if it's an unpublished node.

Another approach would be to let tac completely ignore $node->$status like its implemented now and only restrict access to unpublished modules via another module.

However, I would appreciate if you could get the current behaviour across to me... For rid 1 and 2 (anonymous and authenticated users) tac writes permissions into node_access no matter if a node is published or not but for roles depending on tags it only writes permissions if the node is published. So, to extend my previous example if I have tagged node#11 and configured tac in a way that role#6 has update=allowed for that term I get the following additional line if I publish my node:

11 6 term_access 0 1 0

This line is getting removed from node_access if I unpublish node#11.

Thank you.

xjm’s picture

Issue tags: -d7 release

(Removing tag as this is not a release blocker.)

TAC doesn't check $node->status at all, and should not alter or override the core behavior. Keep in mind that TAC is actually currently supporting the core feature (since the core philosophy is that only administrators can access unpublished content), and it is view_unpublished that deviates from the core behavior.

The correct solution to integrate the two modules will be a hook_node_grants_alter() so that TAC can override view_unpublished's 0 for grant_update. We might also want to abstract this case to integration with other node access modules.

For rid 1 and 2 (anonymous and authenticated users) tac writes permissions into node_access no matter if a node is published or not but for roles depending on tags it only writes permissions if the node is published. So, to extend my previous example if I have tagged node#11 and configured tac in a way that role#6 has update=allowed for that term I get the following additional line if I publish my node:

11 6 term_access 0 1 0

This line is getting removed from node_access if I unpublish node#11.

This is strange. There is no code in TAC that would make this distinction. This behavior could even be coming from node.module. I will need to look into it further.

sebastian.haas’s picture

TAC doesn't check $node->status at all, and should not alter or override the core behavior.
But because tac doesn't check $node->status it grants view permissions like for any other published note which does override the default core behaviour in my case as it doesn't matter whether a node is published or not, it's treated the same way. So with tac enabled, there is basically no use for a node's status anymore as all nodes are being published because of the View=allow permission-

xjm’s picture

TAC doesn't check $node->status at all, and should not alter or override the core behavior.
But because tac doesn't check $node->status it grants view permissions like for any other published note which does override the default core behaviour in my case as it doesn't matter whether a node is published or not, it's treated the same way. So with tac enabled, there is basically no use for a node's status anymore as all nodes are being published because of the View=allow permission-

I was about to reply that this is not correct... then I tested it and confirmed it is correct. In Drupal 6 and prior, the node published state overrode any and all node access grants, from any access control module, so TAC could not grant access to unpublished nodes, no matter what. That's why view_unpublished came into existence. The fact that this appears to be different in Drupal 7 concerns me, because if I was not aware of it as a developer, I am certain many users will also not be aware of it. I am not sure if it is an intended core behavior change; I need to look into it more.

Edit: This is probably the reason for the difference between the Drupal 6 and Drupal 7 behavior with view_unpublished. Now view_unpublished is just another node access control module, and so collides with TAC in the grants table like any other node access control module. So the correct answer is still a hook_node_access_alter().

xjm’s picture

Found #920614: Unpublished content visible when a Node Access module is enabled, which is marked fixed, but... I will try to get some answers on this from core developers today.

Edit: http://drupal.org/node/920614#comment-3499932 is enlightening.

sebastian.haas’s picture

So what can I expect from tac in case I have to develop some helper module. Will the current behaviour remain in the d7 release?

But no matter if tac overrides unpublished nodes or ignores them, we will have to fix that issue I posted in #8 at the bottom because it makes it impossible to control unpublished nodes via terms.

xjm’s picture

I would have marked it "Won't Fix" if I wasn't going to work on a solution. :) I just need to fix all these form errors first (#1214472: Undefined index: und in taxonomy_access_form_alter() -- incorrect assignment of language key etc.), and (now) get feedback from some core folks about what the design behavior of the unpublished state is.

xjm’s picture

DamZ explained this to me. A separate issue has been opened at #1216212: Do not indiscriminately override unpublished state. How we handle that will dictate the solution to this as well.

xjm’s picture

After thinking about this a bit, I think that maybe the best solution would be for TAC to provide a permission, "Use TAC grants for unpublished nodes" or something. That'd eliminate the need for view_unpublished entirely.

Edit: code snippet was incorrect. However, it would still be simple to implement.

One would simply add this permission to the given role at admin/people/permissions. What do you think? Would this work for your use case?

sebastian.haas’s picture

Sounds good although I'm not sure if I fully understand what you're saying.
So I just write down a few ideas of mine:

  • There is a flag in tac administration that controls whether tac creates grants for unpublished nodes in table node_access or not. That MUST include those for role 1 and 2 and is kind of a global setting for node access management.
  • In case this flag is set, it is necessary to add a simple condition to that part of the code that actually grants a user the right to view/edit/delete a node. Maybe this could be done in hook_node_access, please correct me if I'm wrong. In that condition we have to check whether it's an unpublished node or not, and in case it's unpublished we have to deny access in general to restore the original functionality of published/unpublished. But this means we have to overrule any grants from node_access. In a typical setting, there might be a view=access in role 2 and 1 for every node, which would have to be overruled.
  • The next step would be to check if the particular user has this "view unpublished" permission set in any of his roles (admin/people/permissions). In that case we would have to apply the given rights from node_access and NOT overrule them as described above. (rights set via taxonomy)

But we still have to fix the issue from post #8.

xjm’s picture

Try testing the patch in #1216212: Do not indiscriminately override unpublished state and seeing if it can provide the behavior.

  1. Disable view_unpublished.
  2. Apply the patch.
  3. For any role that would previously have had view unpublished permission, instead check the box for "Use Taxonomy Access Control grants for unpublished nodes" at admin/people/permissions#module-taxonomy_access
  4. Rebuild node access at admin/reports/status/rebuild. (This step is necessary due to a deficiency in the patch; for testing, rebuild each time you change the permissions at admin/people/permissions#module-taxonomy_access).
sebastian.haas’s picture

xjm’s picture

So, based on #962664: Taxonomy Index for unpublished entities, here is what I think an interim solution will have to look like.

  1. Create a separate submodule, taxonomy_access_unpublished.
  2. The submodule implements the permission we tried to add in #1216212: Do not indiscriminately override unpublished state.
  3. For the time being, the submodule requires taxonomy_entity_index (henceforth TEI). Note: TEI adds overhead. For most sites it is probably not a huge concern, but for a high-traffic site, or a site with a very large number of nodes or users, it would be better to instead maintain a patch against core (from #962664: Taxonomy Index for unpublished entities).
  4. The submodule does a hook_query_TAG_alter() on TAC's node access queries to make it use TEI the table instead of {taxonomy_index}. (Aside: This function would be named taxonomy_access_unpublished_query_taxonomy_access_node_access_alter(). Yowza. I wish for the hundredth time the module were named tac instead of taxonomy_access.)
  5. The submodule implements hook_node_access_records_alter() to update TAC's grants to the logic we originally provided in #1216212: Do not indiscriminately override unpublished state.
  6. The submodule rebuilds the appropriate node access when the permission configuration for its permission is updated, using TAC's API.

This is a not-insignificant undertaking, so I don't know how soon I could work on it. The roadmap is there, though, if you want to try your hand at a patch.

sebastian.haas’s picture

Ad 4.: I think this is not possible in that way because of

/**
 * Implements hook_node_access_records().
 */
function taxonomy_access_node_access_records($node) {
  if ($node->status) {
    return _taxonomy_access_node_access_records($node->nid);
  }
}

since I cannot alter a query which is never built... Are you sure you meant taxonomy_access_unpublished_query_taxonomy_access_node_access_alter()?

xjm’s picture

Why do you say the query is not built? That function calls TAC's query builder. The change needs to be module-wide because we are entirely changing the database table used.

sebastian.haas’s picture

Oh I think I understated my point. Whatever, I have written a small module that depends on taxonomy_access and taxonomy_entity_index which does exactly what I want here.
It's a slightly different approach than what you described in #20, but as I mentioned I don't think hook_query_TAG_alter() will work in that case. I'm also using the node_entity_index table for unpublished nodes only, so 90% is done by tac- I'm using almost everything of it's api to build permissions.

I can upload the module here if you want me to-

xjm’s picture

Edit: I think I understand the confusion now. My suggestion was to add the query alter and make your own call to _taxonomy_access_node_access_records() in the hook_node_access_records_alter(). You need to do this so the submodule is robust. Otherwise it will break any time the query is used outside this specific case. Edit: the affected nodes queries would also need to be rewritten as dynamic queries and tagged, I think. Otherwise your nodes will not be updated properly when there are taxonomy or configuration changes. Edit 2: looks like they are already dynamic, so they just need a tag added.

Ping me on IRC if you like and I can explain further.

xjm’s picture

I just want to reiterate if you create a submodule without including the query alters, it will make your node access broken whenever any change is made other than via node_save(). Say, if the user changes a role's TAC configuration, or deletes a taxonomy term. All the other queries will not select from the unpublished nodes' relationships.

xjm’s picture

I posted #1220610: Tag all queries on {taxonomy_index} so that all queries against {taxonomy_index} can be tagged with taxonomy_access_node and thus manipulated from another module.

sebastian.haas’s picture

Thank you for #1220610: Tag all queries on {taxonomy_index}, this made my life much easier. I think the global query alteration could be done like this:

/**
 * Implements hook_query_TAG_alter()
 */
function taxonomy_access_unpublished_query_taxonomy_access_node_access_alter(QueryAlterableInterface $query) {  
  if($query->hasTag('taxonomy_access_node')) {
    $tables =& $query->getTables();
    $tables['ti']['table'] = 'taxonomy_entity_index';
    $cond =& $query->conditions();
    foreach ($cond as &$i) {
      if($i['field'] == 'ti.nid') {
        $i['field'] = 'ti.entity_id';
      }
    }
  }
}

Iterating over the conditions array might be unnecessary since I did not found any other occurence of ti.nid than in _taxonomy_access_node_access_records so the array size should be constantly 2...

xjm’s picture

Yep -- though the new tag is more generic, so we thankfully got the hook name down to taxonomy_access_unpublished_query_taxonomy_access_node_alter().

I would keep the foreach loop, as it makes the hook more robust going forward as new functionality is added. Actually, for best practice, we probably want to loop over $tables looking for taxonomy_index and collect those aliases (since it is possible to join on the same table twice), then set them to the new table.

Also, I did not realize that TEI names the id field differently (though... of course, it has to). This means we need to check not only conditions, but also fields and joins, and replace the nid with $alias.entity_id as you have above.

sebastian.haas’s picture

StatusFileSize
new37.27 KB

Thanks for your input.

I extended the code from above:

/**
 * Implements hook_query_TAG_alter().
 */
function taxonomy_access_unpublished_query_taxonomy_access_node_alter(QueryAlterableInterface $query) {  
  // Taxonomy access unpublished does a hook_query_TAG_alter() on Taxonomy access control's 
  // node access queries to make it use {taxonomy_entity_index} instead of {taxonomy_index}.
  if ($query->hasTag('taxonomy_access_node')) {
    // Iterate over tables, searching for {taxonomy_index} in order to replace it with TEI
    // Also, keep the table aliases used for {taxonomy_index} in $ti_aliases
    $ti_aliases = array ();
    $tables =& $query->getTables();
    foreach ($tables as &$tbl) {
      if ($tbl['table'] == 'taxonomy_index') {
        $ti_aliases[] = $tbl['alias'];
        $tbl['table'] = 'taxonomy_entity_index';
      }
    }
    // Iterate over conditions in order to change alias.nid to alias.entity_id 
    $cond =& $query->conditions();
    foreach ($cond as &$c) {
      foreach ($ti_aliases as $alias) {
        if ($c['field'] == $alias.'.nid') {
          $c['field'] = $alias.'.entity_id';
          break;
        }
      }
    }
    // Iterate over fields in order to change alias.nid to alias.entity_id 
    $fields =& $query->getFields();
    foreach ($fields as &$f) {
      if ($f['field'] == 'nid' && in_array($f['table'], $ti_aliases)) {
        $f['field'] = 'entity_id';
      }
    }
  }
}

This solution should also be alias-independent as I cached them for later use.
What do you mean by checking joins? I don't think it is necessary/possible to check them explicitly as there is a join type field in the $tables array that defines the type of join. I attached a screenshot of the $tables array to illustrate this.

I also implemented hook_node_access_records_alter() and wrapped everything into a module with a dependency to tac and tei. But since I'm about 10 days into drupal community I don't really know what to do next. I can post my code here or give it to you to integrate it with tac or to open a new module, or I can open a new module on my own.
Or in case you're thinking it's not useful/practicable at all, I can just forget about it and only use it internally.

xjm’s picture

Title: Integration with module view_unpublished » Provide access control for unpublished nodes

The code above looks like the correct solution as far as I can tell at first read. Regarding the joins, when you complete the modifications in the hook and do a $query->__toString(), does it correctly use the new table in in that query even when the table is not the main FROM?

I also implemented hook_node_access_records_alter() and wrapped everything into a module with a dependency to tac and tei. But since I'm about 10 days into drupal community I don't really know what to do next. I can post my code here or give it to you to integrate it with tac or to open a new module, or I can open a new module on my own.
Or in case you're thinking it's not useful/practicable at all, I can just forget about it and only use it internally.

It's definitely worth using as a TAC extension module until #962664: Taxonomy Index for unpublished entities is fixed. I can maintain it, since ideally I'd like to integrate the functionality into TAC once TEI is no longer required. Go ahead and upload an archive of it as an attachment here once you're ready so that I can review it.

sebastian.haas’s picture

StatusFileSize
new2.27 KB
new16.31 KB

ad query: Looking good to me as far as I can tell. (see screenshot)

ad module: I attached a basic version of taxonomy_access_unpublished, but there are still a few tasks to do... I'm going to post them in the issue tracker as soon as you open the new module.

//EDIT: Ahm.. I forgot to remove the dpm($query->__toString()) call in line 143. :/ Sorry about that.

sebastian.haas’s picture

Just for the sake of completeness: I've done a patch for TEI which allows to trigger an index rebuild. Otherwise {taxonomy_entity_index} is only getting updated if you update an entity itself, so it is useful for tac testing purposes to do a re-index of TEI. The admin page can be accessed via admin/config/system/taxonomy_entity_index

See also #1207794: Provide administration form to execute reindexing batch

xjm’s picture

Status: Active » Needs review

Excellent! I'll do a code review and some testing when I get the chance. If everything looks good, we can open a project for it.

We'll want to implement a hook_menu() and move the config to admin/config/people (per http://drupal.org/node/549094#config-people), and probably as a sub-tab under TAC's config. I've been meaning to add additional tabs to TAC's config for awhile anyway, though, so I'll work that issue out then.

sebastian.haas’s picture

We'll want to implement a hook_menu() and move the config to admin/config/people (per http://drupal.org/node/549094#config-people), and probably as a sub-tab under TAC's config. I've been meaning to add additional tabs to TAC's config for awhile anyway, though, so I'll work that issue out then.

I'm not sure if you got my point, the admin/config/system/taxonomy_entity_index stuff is for Taxonomy Entity Index only, Taxonomy Access Unpublished has no admin ui on it's own, just an additional permission which is set via hook_permission() so I don't think that a re-index command which is only TEI related should be placed in a sub-tab of TAC.

I took a look into http://drupal.org/node/549094#config-people and found it most suitable to place it into admin/config/system. However, it's just TEI stuff so this is not in our backyard.

xjm’s picture

Ah, I misunderstood. I see now. Up to davereid, then.

sebastian.haas’s picture

Hm. Those language barriers... :( I think you might want to review and fix my documentation and hook_help() text as well.

xjm’s picture

Status: Needs review » Fixed

Ta-da!
http://drupal.org/project/taxonomy_access_unpublished
http://drupal.org/user/1058828/track/code

I made a few changes:

  1. Cleanup for various Drupal standards (code and otherwise).
  2. Changed the human-readable name to TAC Unpublished" for fewer keystrokes.
  3. Rewrote the hook_help() a little.
  4. Removed some extraneous stuff (unneeded .install file, nonexistent configuration path, etc.).
  5. Discovered grant alter hook was bugged, and consequently...
  6. Replaced the grant alter hook and its helper with a much simpler version based on my original patch attempt from #1216212: Do not indiscriminately override unpublished state.

I also made a TAC commit to support caching role lists per permission.

So far, the query alter seems to be working nicely. I tested it for anonymous and for a custom role. There's one outstanding issue (no node access update on permission change) but we can open that issue against the new project!

Status: Fixed » Closed (fixed)

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

shaundychko’s picture

I've been using the module grants module http://drupal.org/project/module_grants to deal with node grants on unpublished content. I've tested it with TAC on unpublished content on D6.

xjm’s picture

#39: This issue was specific to D7, which completely changed the interactions between taxonomy, unpublished content, and node access.

csuggs4’s picture

Issue summary: View changes

Instead of using taxonomy_access_unpublished, why not just remove the conditional in the code snippet in #21? Needing this functionality on a very large multi-site, and worried about the non-trivial overhead from taxonomy_access_unpublished.

Thanks!