Problem

In Drupal 8, properties on entities (such as nodes) are planned to vary by language. Key properties like status and author govern basic permissions to nodes, and if those properties can vary by language, the permission system needs to take additional information ($langcode) for the node to check the permissions with the right variant. Essentially entities will have variants of any of their underlying values (properties/fields) by language, and permissions should be granular to the language variant.

Just as with making the entity label handling aware of multilingual (see #1616952: Add a langcode parameter to EntityInterface::label()), we should start making the node access system multilingual step by step.

Proposed solution

Add optional $langcode to the node access system. As a first step, this issue deals with introducing $langcode to node_access() and the APIs it uses/defines for runtime permission checking. It uses the entity getters to get status and author values for specific languages (which makes this code vary those values by language later when those getters will actually return values that vary by language). New language-aware caching of permissions is also introduced.

The patch does not yet introduce langcode to the database stored per-node access values. See followups.

The $langcode should default to the original submission language code of the entity, which makes the introduction of this feature backwards compatible and keeps the API easy to use. We should take care of invoking node_access() later with specific language codes.

Followups

Following this issue, we should implement node revisions access support as well, see #1667614: Make node revision access language aware.

The next step after that would be making the node_access database storage (and its manipulation APIs) multilingual-aware, which is happening at #1658846: Add language support to node access grants and records.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Status: Active » Needs review
FileSize
5.11 KB

First quick untested patch.

Gábor Hojtsy’s picture

Looking around more in the node access code, looks like the rest of the code deals with individual nodes, for which I think its best to work out a solution in a followup (eg. should grants be language dependent or only access records; how to make language-wildcard access work on the record level, etc). To avoid those detailed discussions here and introduce a high level support first I think the scope of this issue looks clear.

I've opened #1658846: Add language support to node access grants and records for the per-grant/record support as a followup. That one covers the only @todo in the patch.

Now reviews are welcome!

agentrickard’s picture

Well, there is also a major architecture question about whether the individual rules applied via hook_node_access() should continue to be supported. The main problem with that access hook is that it makes generating lists of content virtually impossible.

Gábor Hojtsy’s picture

@agentrickard: how is that related to supporting language? Any comments on the proposed changes?

agentrickard’s picture

It's related because it changes the nature of this patch if we ditch hook_node_access().

The problem I have with this approach -- like many others when touching on a language context -- is that it treats language as a special case. I understand that, due to the limits of node_access, we cannot consider language to be a realm (since the permissive OR nature of grants enforcement won't allow it).

It would be cleaner, perhaps, to allow AND grants to be enforced (something I've advocated since Drupal 6).

As for hook_node_access(), having $langcode should be irrelevant, since the $node object should already have a language property.

agentrickard’s picture

Scratch the last part of that. I can see a use-case for asking whether a node is visible in a different $langcode.

Gábor Hojtsy’s picture

@agentrickard:

As for hook_node_access(), having $langcode should be irrelevant, since the $node object should already have a language property.

That is not actually the case. The Drupal 8 target is to make nodes vary by language entirely (D7 already made fields vary by language but not base node properties). So imagine status and author will vary by language, which necessitates optional external information for cases where you want to look at the non-default variant of the node. The default variant will keep being the original language variant of the node as originally submitted, for which indeed the langcode on the node is indicative of the langcode. The fallback logic for that is included in the patch.

In other words, all systems that would want to support language variance will need to treat nodes as identifiable with nid PLUS langcode to identify the variants stored within the node. Indeed, nodes will not support variants stored by other dimensions, at least I don't know of plans to that effect.

So what the patch does is effectively adding an optional langcode which defaults to the node's original language (and due to that is backwards compatible as-is). The langcode argument is passed around to let all code react to requests for specific language variants of the node for access checking. For example, the status and uid properties of the node are accessed with their getters passing on langcode to get the right language value.

agentrickard’s picture

Agreed on that point.

agentrickard’s picture

Gabor and I discussed this on IRC. The move in Drupal 8 is to make $langcode a part of the entity_id key. E.g., instead of just having $node->nid as a foreign key, the key will be $node->nid + $node->langcode.

This patch just pushes that direction forward, so it looks good conceptually.

Gábor Hojtsy’s picture

Minor phpdoc extension to explain optional langcode even better.

agentrickard’s picture

Minor changes:

+  // to an empty langcode if a node type was requested. The later is purely

Use "latter" not "later" here.

Gábor Hojtsy’s picture

Updated with that minor change.

xjm’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The following review is just from reading the patch; I will take a closer look in context later.

  1. We definitely need to add test coverage for this.
  2. +++ b/core/modules/node/node.api.phpundefined
    @@ -604,7 +606,7 @@ function hook_node_load($nodes, $types) {
    -function hook_node_access($node, $op, $account) {
    +function hook_node_access($node, $op, $account, $langcode) {
    
    +++ b/core/modules/node/node.moduleundefined
    @@ -2848,13 +2848,17 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
    -function node_access($op, $node, $account = NULL) {
    +function node_access($op, $node, $account = NULL, $langcode = NULL) {
    

    I noticed that the parameter for the hook is non-optional despite that the argument to node_access() is optional. Gábor and I discussed this in IRC, and he pointed out that the same is true of the $account parameter.

    In reality the hook definition is merely documentation--an module can declare a hook implementation with fewer than the recommended arguments and it will work just fine--so it doesn't matter too much either way.

  3. +++ b/core/modules/node/node.moduleundefined
    @@ -2872,18 +2876,25 @@ function node_access($op, $node, $account = NULL) {
    -  if (isset($rights[$account->uid][$cid][$op])) {
    -    return $rights[$account->uid][$cid][$op];
    +  if (isset($rights[$account->uid][$cid][$langcode][$op])) {
    +    return $rights[$account->uid][$cid][$langcode][$op];
    ...
    -    $rights[$account->uid][$cid][$op] = TRUE;
    +    $rights[$account->uid][$cid][$langcode][$op] = TRUE;
    ...
    -    $rights[$account->uid][$cid][$op] = FALSE;
    +    $rights[$account->uid][$cid][$langcode][$op] = FALSE;
    
    @@ -2892,24 +2903,25 @@ function node_access($op, $node, $account = NULL) {
    -    $rights[$account->uid][$cid][$op] = FALSE;
    +    $rights[$account->uid][$cid][$langcode][$op] = FALSE;
    ...
    -    $rights[$account->uid][$cid][$op] = TRUE;
    +    $rights[$account->uid][$cid][$langcode][$op] = TRUE;
    ...
    +    $rights[$account->uid][$cid][$langcode][$op] = TRUE;
    
    @@ -2937,13 +2949,13 @@ function node_access($op, $node, $account = NULL) {
    -      $rights[$account->uid][$cid][$op] = $result;
    +      $rights[$account->uid][$cid][$langcode][$op] = $result;
    ...
    -      $rights[$account->uid][$cid][$op] = TRUE;
    +      $rights[$account->uid][$cid][$langcode][$op] = TRUE;
    

    For these it would perhaps be better to make the $langcode a part of the $cid rather than a separate level of nesting?

  4. +++ b/core/modules/node/node.moduleundefined
    @@ -2892,24 +2903,25 @@ function node_access($op, $node, $account = NULL) {
    +  // @todo: add langcode support to node_access schema / queries.
    

    Presumably this refers to #1658846: Add language support to node access grants and records, right?

    There should be no colon after @todo, and Add should be capitalized. I would actually also like to see this @todo at the end of the function docblock.

Gábor Hojtsy’s picture

@xjm: thanks! First of all I've updated the issue summary based on our conversation.

1. I'm not really sure what to test here, the properties cannot vary yet by language, so if you use it with various languages, it will still do the same thing. There is possibly some value in testing that (eg. with a language that is not present on the node, one that is, etc). Mainly I think the key point is that it should still work as-is.

2. The hook also has a different argument order vs. node_access(). I'm fine with documenting the hook differently. I think the main point of it being defined like this is that the hook implementor does not need to take care of defaulting the values if not provided, since they can be sure they are always provided. Less code in implementations of the hook (even though it makes it inconsistent with node_access()).

3. Its logically the same thing, if its a separate cid, or one more level under the cid, its either represented on the same array level or one more array level. It is the same granularity required either way. Since all other components are nested levels, such as $op, it seems inconsistent to not nest for $langcode. We can also flatten the whole thing and store in one array to make it consistent that way, but flattening by one dimension while we leave the others nest looks inconsistent to me.

4. Thanks for the comments on formatting, indeed it refers to #1658846: Add language support to node access grants and records.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
991 bytes
4.99 KB

Changed the @todo. I've argued about the hook required params / order simplifying implementation and the nested keys being as-is in the hook. We discussed the $cid being a flat key in node revisions access, but not here. So only adding tests seems to be left, unless more people argue its better to make hook_node_access() implementations more complex in the name of consistency in optional arguments. Marking needs review for testbot, but it should just pass as earlier.

Gábor Hojtsy’s picture

Assigned: Unassigned » Gábor Hojtsy
Status: Needs review » Needs work

Will work on tests today.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
9.23 KB

Attached patch does not change anything in the code but adds tests:

- creation of a Hungarian node and verification that it is accessible the same way regardless of language
- addition of a hook_node_access() implementation that denies all operations on Catalan nodes
- test that Catalan is not accessible anymore

This test should be extended in #1658846: Add language support to node access grants and records, once the node access records system has language support too. This patch only deals with adding support in the runtime checking.

Anything else needed here?

effulgentsia’s picture

Status: Needs review » Needs work
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
@@ -0,0 +1,86 @@
+    $base_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);

I think $expected_node_access would be a clearer variable name here.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
@@ -0,0 +1,86 @@
+    // Tests that Catalan provided specifically results in the same. The node
+    // has no Catalan property support yet, but all properties are returned
+    // regardless of language with their original values.
+    $this->assertNodeAccess($base_node_access, $node, $web_user, 'ca');
+
+    // Tests that Croatian provided specifically results in the same. The setup
+    // has no Croatian language configured, but all properties are returned
+    // regardless of language with their original values.
+    $this->assertNodeAccess($base_node_access, $node, $web_user, 'hr');

I don't get either of these 2 comments. It looks like all we're asserting is that when we haven't yet set 'node_access_test_secret_catalan' that special Catalan logic isn't invoked, but the comments seem unrelated to that.

+++ b/core/modules/node/node.api.php
@@ -596,6 +596,8 @@ function hook_node_load($nodes, $types) {
+ * @param object $langcode
+ *   The language code to perform the access check operation on.

I think this doc can be improved, but I don't have a specific suggestion at this time, and I'm okay with letting it go until a follow-up.

+++ b/core/modules/node/node.module
@@ -2872,18 +2880,25 @@ function node_access($op, $node, $account = NULL) {
+  // If no language code was provided, default to the node's langcode or
+  // to an empty langcode if a node type was requested. The latter is purely
+  // for caching purposes.

This is awkward, and so I agree with xjm that we should instead do something like

if (is_object($node)) {
  if (!isset($langcode)) {
    $langcode = $node->langcode;
  }
  $cid = $node->nid . ':' . $langcode;
}
else {
  $cid = $node;
}

I don't see that as causing inconsistency, because $account is who we're determining access for, and $op is what kind of access, but $node->nid and $langcode jointly make up the thing we're checking access to.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
9.02 KB
2.63 KB

1. Changed to $expected_node_access.

2. What we are testing is that a node not having any specific values in itself to deny access in a specific language (which it will possibly have once translatable properties are in) is accessible the same way with any existing (ca) or non-configured (hr) language, it does not differentiate its access. Eventually access checking on nodes will be (A) based on possibly language specific node properties (B) based on runtime node access hooks (C) based on language specific node access storage. This issue deals with (B), since we don't have language specific properties yet and (C) is coming with #1658846: Add language support to node access grants and records. The two asserts you quoted are about testing that (A) is not yet present so regardless of the language used works the same way. I tried making the code comments better for these.

3. Let me know if you have any improvement suggestions.

4. We've actually discussed this with @xjm further, and she remembered this kind of concatenation and flattening is used elsewhere. This can happen in a followup. The uid, etc. is flattened to the same level at other places in the node access system, and there is no differentiation of kinds of identifiers in that. See http://api.drupal.org/api/drupal/modules%21node%21node.module/function/_... for example. So I think we should keep all on the same level here, and if people want to see this code use concatenated IDs on the same level, do it in a different patch. I'm just applying the existing pattern here.

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

:)

webchick’s picture

Title: Make node_access() language-aware » [Change notice] Make node_access() language-aware
Priority: Normal » Critical
Status: Reviewed & tested by the community » Active
Issue tags: +Needs change record

This seems to be a pretty straight-forward change, and has approval from a couple of the node access system maintainers.

Committed and pushed to 8.x. Seems like we will need a change notice.

tstoeckler’s picture

Title: [Change notice] Make node_access() language-aware » [Change notice + Missing files] Make node_access() language-aware
Status: Active » Needs work

If I'm not mistaken the added test classes were not committed. Adding that to the title.

webchick’s picture

Title: [Change notice + Missing files] Make node_access() language-aware » [Change notice] Make node_access() language-aware
Status: Needs work » Active

Oops! Added the missing file and committed and pushed it to 8.x. Sorry. :(

Gábor Hojtsy’s picture

Priority: Critical » Normal
Status: Active » Fixed
Issue tags: -Needs change record, -sprint
Gábor Hojtsy’s picture

Title: [Change notice] Make node_access() language-aware » Make node_access() language-aware
Gábor Hojtsy’s picture

Issue summary: View changes

Update with more details.

Status: Fixed » Closed (fixed)

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

salvis’s picture

I updated Devel Node Access to this level, see #1727532: Update DNA for language-aware node access

salvis’s picture

Issue summary: View changes

Add revsions followup issue.