Problem/Motivation

The node access system is great for having granular node access controls with different realms and grants, however it is not yet compatible with the entity translation system (that was introduced in Drupal 7). The regular node translation system uses separate node copies which is naturally compatible with node access (due to the keys being the node IDs), however with the entity translation system, a node can have many language variants and it is not possible to have separate access values for them.

Trying to work around the problem by introducing language specific grants and realms was discussed and discarded, because the grant/realm system is additive, so if a grant/realm grants access to a node, other grants/realms are not considered. So if access is provided to a node via any language, the rest of the languages would not be inaccessible either. That is not a good fit for the granularity needed.

The legacy translation module, which is hidden, cannot be removed until this issue is fixed.
#1952044: Migration path for legacy content translation module data and #1952062: Remove legacy translation module in favor of content translation postponed on this issue.
#1860522: [META] support (basis for a contrib) Translation Editorial Workflow (support for revisions for translations of entities) lists this issue as a use case.

Proposed solution

We propose the stored node access system to introduce a langcode column to serve as part of a compound key for node access, allowing more granular access control. Records for all language variants of a node should be saved, and the original language version of the entity should be marked as a fallback item, like so:

The langcode column is used to be able to pick out specific grants and realms only for specific language versions. The fallback value is used when no language condition is provided/available, so as to serve as the master access value in that case. The specific access and node display fallback logic takes care of displaying the right language values later. There is not much better we can do if there is no specific language condition set.

API changes

The proposed patch changes hook_node_grants() so the return value can now contain langcode specific grants, like so:

function example_node_access_records(Drupal\node\Node $node) {
  // ...
  $grants[] = array(
    'realm' => 'example',
     'gid' => 1,
    'grant_view' => 1,
    'grant_update' => 0,
    'grant_delete' => 0,
    'langcode' => 'it',
  );
  // ..
  return $grants;
}

If a langcode is not provided than the node's original submission language ($node->langcode) is assumed. If the langcode equals the node's original submission language, a fallback = 1 value is automatically added, otherwise the fallback flag is 0.

This is a followup to #1658814: Make node_access() language-aware.

Issues blocked by this one

Follow-up Issues

Related Issues

CommentFileSizeAuthor
#210 language-grants-1658846-210.patch62.2 KBYesCT
#210 interdiff-207-210.txt24.61 KBYesCT
#207 language-grants-1658846-205-207.interdiff.txt7.16 KBpenyaskito
#207 language-grants-1658846-207.patch62.03 KBpenyaskito
#205 language-grants-1658846-205.patch61.35 KBeffulgentsia
#205 interdiff.txt728 byteseffulgentsia
#203 language-grants-1658846-203.patch60.52 KBeffulgentsia
#203 interdiff.txt12.33 KBeffulgentsia
#198 language-grants-1658846-198.patch60.57 KBeffulgentsia
#195 language-grants-1658846-195.patch60.48 KBpenyaskito
#181 interdiff.txt12.21 KBpenyaskito
#181 language-grants-1658846-181.patch60.41 KBpenyaskito
#175 interdiff.txt715 bytesGábor Hojtsy
#175 language-grants-1658846-176.patch60.27 KBGábor Hojtsy
#173 interdiff.txt5.49 KBGábor Hojtsy
#173 language-grants-1658846-173.patch60.27 KBGábor Hojtsy
#166 interdiff-152-166.txt2.28 KBSchnitzel
#166 language-grants-1658846-166.patch59.45 KBSchnitzel
#152 interdiff-146-152.txt8.41 KBSchnitzel
#152 language-grants-1658846-152.patch59.13 KBSchnitzel
#146 language-grants-1658846.interdiff.145-146.txt12.43 KBSchnitzel
#146 language-grants-1658846-146.patch58.58 KBSchnitzel
#145 language-grants-1658846.interdiff.144-145.txt3.06 KBpenyaskito
#145 language-grants-1658846-145.patch53.27 KBpenyaskito
#144 1658846-language-node-144.patch53.11 KBvijaycs85
#144 1658846-diff-139-144.txt1.53 KBvijaycs85
#139 1658846-language-node-139.patch52.58 KBvijaycs85
#137 node-1658846-134-137.interdiff.txt1.23 KBpenyaskito
#137 node-1658846-137.patch52.55 KBpenyaskito
#134 node-1658846-133-134.interdiff.txt613 bytespenyaskito
#134 node-1658846-134.patch52.2 KBpenyaskito
#133 node-1658846-128-133.interdiff.txt2.05 KBpenyaskito
#133 node-1658846-133.patch52.22 KBpenyaskito
#128 node-1658846-125-128.interdiff.txt1.96 KBpenyaskito
#128 node-1658846-128.patch52.6 KBpenyaskito
#125 node-1658846-125.patch52.33 KBpenyaskito
#125 node-1658846-117-125.interdiff.txt2.32 KBpenyaskito
#123 node-1658846-117-123.interdiff.txt2.25 KBpenyaskito
#123 node-1658846-123.patch52.27 KBpenyaskito
#120 node-1658846-117-120.interdiff.txt1.32 KBpenyaskito
#120 node-1658846-120.patch52.94 KBpenyaskito
#118 node-1658846-117-118.interdiff.txt2.4 KBpenyaskito
#118 node-1658846-118.patch52.41 KBpenyaskito
#117 node-1658846-117.patch51.62 KBpenyaskito
#94 node-1658846-94.patch51.63 KBxjm
#86 node-1658846-86.patch56.21 KBxjm
#86 interdiff.txt36.72 KBxjm
#85 node-1658846-85.patch56.14 KBxjm
#85 interdiff.txt632 bytesxjm
#83 node-1658846-81.patch56.14 KBxjm
#83 interdiff.txt632 bytesxjm
#78 node-1658846-78.patch56.14 KBxjm
#78 interdiff.txt1.03 KBxjm
#76 node-1658846-76.patch55.11 KBxjm
#76 interdiff.txt17.58 KBxjm
#72 Node access changes (2).png46.42 KBGábor Hojtsy
#69 interdiff-62-69.txt29.01 KBSchnitzel
#69 drupal-1658846-node_access_grants-69.patch52.7 KBSchnitzel
#67 interdiff-62-67.txt28.56 KBSchnitzel
#67 drupal-1658846-node_access_grants-67.patch52.7 KBSchnitzel
#62 drupal-1658846-node_access_grants-62.patch32.11 KBSchnitzel
#62 interdiff-55-62.patch19.14 KBSchnitzel
#57 drupal-1658846-node_access_grants-57.patch16.36 KBagentrickard
#55 drupal-1658846-node_access_grants-55.patch14.55 KBagentrickard
#54 drupal-1658846-node_access_grants-54.patch14.55 KBagentrickard
#33 interdiff-23-33.txt2.83 KBSchnitzel
#33 drupal-1658846-node_access_grants-33.patch14.64 KBSchnitzel
#26 drupal-1658846-node_access_grants-26.patch14.25 KBagentrickard
#23 drupal-1658846-node_access_grants-23.patch14.22 KBSchnitzel
#19 drupal-1658846-node_access_grants-19.patch14 KBSchnitzel
#18 drupal-1658846-node_access_grants-17.patch13.39 KBSchnitzel
#15 drupal-1658846-node_access_grants-15.patch14.18 KBSchnitzel
#9 drupal-1658846-node_access_grants-9-test.patch3.1 KBdisasm
#9 drupal-1658846-node_access_grants-9.patch8.14 KBdisasm
#5 interdiff-1658846-3-5.txt2.41 KBkalman.hosszu
#5 1658846-5.patch5.04 KBkalman.hosszu
#3 1658846-3.patch5.22 KBkalman.hosszu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Status: Postponed » Active

Unpostpone, since that was committed.

kalman.hosszu’s picture

Assigned: Unassigned » kalman.hosszu

I can dedicate time for this issue

kalman.hosszu’s picture

Status: Active » Needs review
FileSize
5.22 KB

I attached the first version of the patch. Pls review it!

Kalman

Gábor Hojtsy’s picture

Looks like a good start IMHO. Quick code review.

+++ b/core/modules/node/node.api.phpundefined
@@ -233,10 +233,13 @@ function hook_node_grants($account, $op) {
+ * - 'langcode': Optional key. The language code of the grant version. This
+ *   value is set automatically from the $node parameter during the database
+ *   writing.

"during database storage" is likely better, but I'm sure others will have nicer ideas :)

+++ b/core/modules/node/node.api.phpundefined
@@ -233,10 +233,13 @@ function hook_node_grants($account, $op) {
  * When an implementation is interested in a node but want to deny access to
- * everyone, it may return a "deny all" grant:
+ * everyone in Catalan language, it may return a "deny all" grant:

Its not the people who are in Catalan but the node variant. So this would eventually deny access to the Catalan node variant. The comment as-is sounds like it refers to the Catalan people :)

+++ b/core/modules/node/node.moduleundefined
@@ -3404,10 +3404,12 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) {
- *   nid.
+ *   nid. If it contains langcode - and grant doesn't contain it - this will
+ *   written to databse, else an empty string.

@@ -3437,6 +3439,9 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
+        if (!isset($grant['langcode'])) {
+          $grant['langcode'] = isset($node->langcode) ? $node->langcode : '';
+        }

Theoretically the node langcode should always be set, its part of the schema + loading/saving.

Also databse => database.

+++ b/core/modules/node/node.moduleundefined
@@ -3404,10 +3404,12 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) {
- *   following keys: realm, gid, grant_view, grant_update, grant_delete.
+ *   following keys: realm, gid, grant_view, grant_update, grant_delete and
+ *   langcode is an optional key which set automatically from $node parameter.

which *is* set automatically

kalman.hosszu’s picture

Thanks Gabor, I attached a new one.

Gábor Hojtsy’s picture

Issue tags: +Needs tests

Thanks, looks like it it time to move on to updating the queries where the data is used, and then add some tests.

Gábor Hojtsy’s picture

Assigned: kalman.hosszu » Unassigned

Anybody up to help with that?

disasm’s picture

Assigned: Unassigned » disasm
disasm’s picture

attached is a non-functioning tests and patch of what I was working on. I basically copied assertNodeAccess function, and testNodeAccess, and started reworking the tests to work with node_access_grants function. I'm leaving for vacation this weekend for 2 weeks, so I won't be able to work on this anymore until I get back. Un-assigning in case someone else wants to pick up where I left off.

Status: Needs review » Needs work

The last submitted patch, drupal-1658846-node_access_grants-9.patch, failed testing.

kalman.hosszu’s picture

Assigned: Unassigned » kalman.hosszu

I'm back from my holiday.

Gábor Hojtsy’s picture

@kalman.hosszu: can you help review the patch proposed above and bring it forward then?

kalman.hosszu’s picture

Assigned: kalman.hosszu » Unassigned

I will able to continue the work on this after wednesday.

Schnitzel’s picture

Assigned: Unassigned » Schnitzel

working on this

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
14.18 KB

here a first version which was discussed with Gabor:

- There is a new column in the node_access called "fallback", this defines which node_access record should be used if no language metatag is defined in db_select or elsewhere.
- The record with the same language as the original node language ($node->langcode) will be the fallback.
- There is a new Metadata for db_select called "langcode", if this is set the node_access table will use the records with this language. If langcode is not set it uses the fallback records

Open Todos

- right now the fallback column is only used with db_select, have to check if the node_access table is queried elsewhere
- is "langcode" the right Metadata key? Or should we use "access_langcode"?
- It does not test yet what happens if there are two node_access records with the same Nid but different languages. How should we test this? Because as far as I know this is not yet possible via the UI and also not really via code.

Gábor Hojtsy’s picture

Status: Needs review » Needs work
diff --git a/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/Entity.php
index 7e73024..000813d 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -222,7 +222,7 @@ class Entity implements EntityInterface {
     else {
       // If there is a langcode defined for this field, just return it. Otherwise
       // return LANGUAGE_NOT_SPECIFIED.
-      return (isset($this->langcode) ? $this->langcode : LANGUAGE_NOT_SPECIFIED);
+      return LANGUAGE_NOT_SPECIFIED;
     }
   }
 

Looks like an unrelated fix?! Should be its own issue AFAIS.

+++ b/core/modules/node/node.installundefined
@@ -148,6 +148,20 @@ function node_schema() {
+        'description' => 'Boolean indicating whether this record should be used as a fallback.',

I would document here, that "used as a fallback if a language condition is not provided". To explain what does this mean.

+++ b/core/modules/node/node.installundefined
@@ -601,6 +615,36 @@ function node_update_8004() {
 /**
+ * Add language.langcode field to node_access table.
+ */
+function node_update_8005() {
+  // Add the langcode field.
+  $langcode_field = array(
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => 'The {language}.langcode of this node.',
+  );
+  db_add_field('node_access', 'langcode', $langcode_field);
+}
+
+/**
+ * Add language.langcode field to node_access table.
+ */
+function node_update_8006() {
+  // Add the fallback field.
+  $fallback_field = array(
+    'description' => 'Boolean indicating whether this record should be used as a fallback.',
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  db_add_field('node_access', 'fallback', $fallback_field);

Merge these into one update function IMHO.

Also need upgrade path for existing records. Sounds like we could get away by asking for rebuilding the node access records in the update (unless it already happens, which it might in fact be the case).

I'd also add simple upgrade path tests, to see if the data is properly updated after update.

+++ b/core/modules/node/node.moduleundefined
@@ -2517,6 +2517,8 @@ function node_page_default() {
+  dpq($select);
+

debug :)

Schnitzel’s picture

new patch with fixes from @gabors mentions, thanks for review!

the LANGUAGE_NOT_SPECIFIED issue is here: #1738368: Not possible to use the entity getter to retrieve non-translatable field values

Still needs upgrade path tests

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
13.39 KB
Schnitzel’s picture

just did my own review and found this:

function hook_install() {
  // Populate the default {node_access} record.
  db_insert('node_access')
    ->fields(array(
      'nid' => 0,
      'gid' => 0,
      'realm' => 'all',
      'grant_view' => 1,
      'grant_update' => 0,
      'grant_delete' => 0,
    ))
    ->execute();
}

I would add fallback = 1, but what are we using for language? because if we add something (like default language) and a query is made with Metadata langcode, it could return no results, where actually this record is made for return all nodes all the time (eg: no node_access enabled)

--> own conclusion: I changed the default of the fallback column to be 1, so then the default record is set as fallback automatically.
I also checked node_access and I changed it to this:

      $nids = db_and()
        ->condition('nid', $node->nid)
        ->condition('langcode', $langcode);
      if ($node->status) {
        $nids = db_or()
                  ->condition($nids)
                  ->condition('nid', 0);
      }

so actually I'm only adding the langcode check for specific nid checks, not for the global nid=0 check.

attached new patch with node_access() change and fallback default

Status: Needs review » Needs work
Issue tags: -Needs tests, -Entity system, -D8MI, -sprint, -language-content

The last submitted patch, drupal-1658846-node_access_grants-19.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
Issue tags: +Needs tests, +Entity system, +D8MI, +sprint, +language-content

The last submitted patch, drupal-1658846-node_access_grants-19.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
14.22 KB

because I updated node_access() this was obviously that it failed.
So the reason is, that for the requested languages there is no node_access record, so the user gets now an access denied.
which @gabor and I both agree that this is the expected behavior.

attached patch changes the test

agentrickard’s picture

I really dislike the use of a separate "fallback" column. Is it not possible to store that information in the langcode column?

If it is not, do we have a better, more consistent column name to use? (e.g. "default" or "is_default")

agentrickard’s picture

Note #2: The use of "langcode" is consistent with the rest of core. No need to change the name of that column (reference comment 15).

agentrickard’s picture

Looked at this more in-depth today, and I still don't understand the "fallback" column. In my tests, all node access records validate as "fallback". This is due to the logic used for determining fallback, which is:

if ($grants['langcode'] == $node->langcode)

I fail to see any cases where this might be not be true, which means that every {node_access} row is marked as 'fallback.' Instead, I am experimenting with setting the original node as the fallback. That code looks like so:

if (empty($node->tnid) || $node->nid == $node->tnid)

Here's a revised patch with that approach, plus an update to HEAD, and a fix for some if/else code format errors.

However, I also fail to see any cases where a record with a duplicate nid but a unique langcode can be saved. Is there something I'm missing regarding the translation system that would allow for that (say, selecting Multiple languages for a node)?

I see the benefit to adding langcode regardless, since it allows language-based access to site alongside other access control rules. (I have a module for testing that I need to sandbox to help others with this.)

agentrickard’s picture

See https://drupal.org/sandbox/agentrickard/1747908 for a sandbox module that implements node access for testing.

Gábor Hojtsy’s picture

@agentrickard: Thanks for the review! The problem is if there is a specific language provided by the node access query alter (by added metadata on the query), it can filter down to one item per node easily. That is "trivial". But then if no language is provided (which is a very-very common use case, like a view without language filtering), then you get multiple entries per node. Then you need *some* way to pick from those entries. That is the hard part of this issue really to crack. The idea of the fallback language was to 'cache' the "original submission language of the node" "flagged" there. So we'd check the permission for that version. The main issue is that the language selection happens later in the rendering phase, so this way even though you might get a French version of the node displayed later, if you don't have access to the original version (let's say German), then you'll not see the node. We need to figure this out somehow.

Any great ideas?

Schnitzel’s picture

@agentrickard

I fail to see any cases where this might be not be true, which means that every {node_access} row is marked as 'fallback.'

yes you are perfectly right, right now I'm did not manage yet to write a test which would test against this, I mentioned this in http://drupal.org/node/1658846#comment-6365354 so I first have to check with fago how to create via the entity api another language for the node so that there are two records in node_access.

if (empty($node->tnid) || $node->nid == $node->tnid)

this will only work for content translation, but not with entity translation (and we try to get rid of content translation).

pp’s picture

Status: Needs review » Needs work
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
@@ -60,30 +65,110 @@ class NodeAccessLanguageTest extends NodeTestBase {
+    $language = (object) array(
+      'langcode' => 'hu',
+    );
+    language_save($language);
+    $language = (object) array(
+      'langcode' => 'ca',
+    );
+    language_save($language);

Be carefully, if #1739994: Use the Language class universally instead of stdObj instances issue will be committed you must correct this lines use "new Language(array(..))" instead of "(object) array(..)"

pp’s picture

Status: Needs work » Needs review

sorry...

tstoeckler’s picture

Well, for completeness' sake, you might as well make the change anyway, since it is the more correct way. Not marking "needs work", though.

Schnitzel’s picture

yes, agree, we should do this, added the new Language object and also fixxed codestyle.
but did not add the changes from #26

agentrickard’s picture

@Schnitzel Yes. Gabor explained that to me in Munich. Backing away from this patch for now...

Gábor Hojtsy’s picture

@agentrickard: @plach is hard at work to bring the entity translation UI into core, in fact the sandbox at http://drupal.org/sandbox/plach/1719670 has some pretty well working setup now (where you can submit and translate nodes :). Hope we can clean this up and include in core soon. However there is no cross-relation between this issue and that one, so we should not stop here in any way. :)

agentrickard’s picture

Right, but it is hard to test the desired behavior given that we don't want to use translation nodes.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

Do we want to admit defeat here or anybody has cleverer ideas to query this data? If we don't do this, the D8 permission system looks like will have language support at some places (eg. runtime checking), but NOT on the stored per node access level. That might or might not be a DX issue as well :/

Gábor Hojtsy’s picture

Issue tags: -sprint

Ok, admitting defeat then. I have little hope this will happen in Drupal 8 given the above lack of interest/concentrated focus. Sounds like language support in node access will be a half beast, only in runtime (that already landed), not in stored node access (that this patch was supposed to achieve) :/

plach’s picture

@Gabor:

Do you think this can be achieved in contrib?

Schnitzel’s picture

@Plach:

Yes this can be achieved in contrib, it will probably not as fast as in core (because additional joins from the node_access table), but it's possible yes.

plach’s picture

Well, it's not critical then. And we might be able to classify this as clean-up/security improvement and work on it after feature freeze.

Gábor Hojtsy’s picture

Uhm, as per my understanding of the node access system, it is not possible. Not sure what Schnitzel is seeing, would love to get an example :)

1. http://api.drupal.org/api/drupal/modules%21node%21node.api.php/function/... provides the records. You can have custom realm's and grant ids (gids). A realm + gid combination is used to give permission based on things like 'you are a member of this organic group' (member = realm, this organic group = gid). So you could *theoretically* bastardise this to be used for language + langcode (however gids can only take integers). Then however, the realm + gid used for a lookup is determined via http://api.drupal.org/api/drupal/modules%21node%21node.api.php/function/... which only gets a user and a node operation. It does not get metadata about the node being viewed/edited/etc. so you cannot tell which language would apply. Finally, even if you could tell, this realm + gid system would not be compatible with other systems. Eg. if an organic group or any other realm based access gives access to the node, it would not care about the language specific access anymore. The node access system grants access I believe if any grant+gid worked out. So

- realm + gid could be bastardized for language, but need to encode langcode as a number
- no way currently to tell which gid to pick, when is a language realm relevant
- even then it is not compatible with other node access systems

Schnitzel, do you see something that I don't or am I in a misunderstanding?

plach’s picture

Ok, and what about

we might be able to classify this as clean-up/security improvement and work on it after feature freeze.

?

Gábor Hojtsy’s picture

I consider this as a pretty big API change / new feature, I'm not sure it would fit under cleanup after December 1st. You can try, but I'd not say I see it likely it would succeed. Also given lack of interest so far anyway, I don't see what would magically change anyway.

plach’s picture

I am interested, it's just that I have more critical issues to work on :)

Gábor Hojtsy’s picture

Yeah, that is how what gets into Drupal 8 is turning out.

agentrickard’s picture

I'm interested in making this happen, but my understanding is that it is blocked because we can't write proper tests because the new translation system -- field based rather than node based -- isn't in yet.

Gábor Hojtsy’s picture

@agentrickard: no, entity language variance is a feature in **Drupal 7** already. We are merely catching up with other subsystems to the previous Drupal version. For example, we added support in core search a month and a half ago: http://drupal.org/node/1737832

The main question remains the data querying. The system currently (without patch) provides node access permissions per node id per operation. It cannot go to under the node level to support language variance (although path aliases, core search, etc. can do that). So the underlying idea is to make the node access system be able to relate operation permissions to a node id + language pair, right. This concept is already in Drupal 7.

However, then there are different type of nodes. There are untranslated nodes (only in one language), there are untranslatable nodes (always language neutral) and there are translated nodes. So a node can be in one or more languages and the one language can be language neutral as well. So the node access table would have a varied number of items for each node (according to the current patch suggested).

So then the problem of querying comes in. If you want a listing filtered to French nodes, we can filter the node access data to French only and only display items which are French. If you want French and language neutral or French and German and Italian (all of which are configurable in a view), then we need to filter for multiple items per node some of which might be contradictory. Eg. if you want either French or German, and a node would not give you access to the French variant but would to the German variant, we need to filter it down to the German variant or deny access?

Finally, then there is the language-free listings, where no language is provided. How should we decide which nodes to give access to? The actual language chosen for display is decide later in the render phase, so even if we decide that the node should get access, what happens if it turns out later it should not have access due to permission restrictions.

So as you might be able to see, this need a lot more information about the language of the query to be passed on to the node access system and possibly change the whole approach as to how node access is stored and retrieved. Its not really a "language people" question as it upsets the whole node access layer. Given that only a minimal number of "language people" were interested in this so far, without a long hard look (and likely extensive work) on this, I don't see it being feasible in Drupal 8.

salvis’s picture

Eg. if you want either French or German, and a node would not give you access to the French variant but would to the German variant, we need to filter it down to the German variant or deny access?

Please help me understand: without node access, if you ask for French or German and a node has both, would you see the node twice?

With node access, if only German were accessible, the node access system would have to return a list of accessible languages (with only 'de' in this case) rather than just a Boolean, right?

What about NA priority? Should it apply per nid or per nid+lang_code? Either one could be completely wrong, depending on your use case...


The case where German is accessible but French is not (even though a French version of the node exists) — is that really relevant in practice? What is the justification for that? Editorial workflow? That's not node access. published already exists as a language-specific property.

Is the German language protected, too? What keeps the user from simply switching to German? If he can do that, then node access is not the proper layer to implement this. Even if you could protect the German language, why would you want to do that?

You will really alienate users on a site if they find out that some content is available in one language but blocked in another (even though it exists!), meaning they have to constantly switch around languages or even maintain multiple accounts in order to get the whole picture.

I've re-read the summary of #1658814: Make node_access() language-aware, but I'm unconvinced that any site would really want to use language-specific node access, especially given its costs in overhead, complexity and maintenance. It's hard enough to keep track of which users have access to which nodes; who would want to multiply that burden and do this per language? and what for?

Sorry if this has been discussed before, but at the point where we are right now, it makes sense to take a step back and reconsider what we're trying to achieve.

Gábor Hojtsy’s picture

Please help me understand: without node access, if you ask for French or German and a node has both, would you see the node twice?

Not by default definitely. If you want to display a node, you give a language and it will attempted to be displayed in that language (and if you don't give a language, the page negotiated content language will be used). If you invoke multiple node displays for the same node with different languages, then yes, that would possibly display the node multiple times, but you asked for that :)

With node access, if only German were accessible, the node access system would have to return a list of accessible languages (with only 'de' in this case) rather than just a Boolean, right?

I don't know. Either the node access system would need to change or the trust in the return value. If you asked for a French node you would not get back the nid. If you asked for a German node and it was accessible, you'd get back that nid. If you did not ask for any specific language, its hard to tell which one the rendering layer would pick later, so we might need to carry around the node knowing we might not display it. That breaks assumptions in the node access system, eg. you would not be able to build a pager system on top of it with predictable page sizes.

What about NA priority? Should it apply per nid or per nid+lang_code? Either one could be completely wrong, depending on your use case...

If you mean "NA priority" = "no language provided in node access lookup" then yeah, that is the most challenging question.

The case where German is accessible but French is not (even though a French version of the node exists) — is that really relevant in practice? What is the justification for that? Editorial workflow? That's not node access. published already exists as a language-specific property.

As per my understanding (and the node access docs), once you use node level access, published is not taken into account and should be implemented as part of your node access. See http://api.drupal.org/api/drupal/modules%21node%21node.api.php/function/...

Note that the system makes no distinction between published and unpublished nodes. It is the module's responsibility to provide appropriate realms to limit access to unpublished content.

It is still part of the primary D8 goals to implement per-language published state for nodes, so if that happens but this does not then sites needing to use per node access will not be able to have different languages in different publication states. Yes, that is a workflow question, not a node access one, but as demonstrated, the node access system stumps on the workflow so it needs to be integrated in the node access system as well.

You will really alienate users on a site if they find out that some content is available in one language but blocked in another (even though it exists!), meaning they have to constantly switch around languages or even maintain multiple accounts in order to get the whole picture.

I've re-read the summary of #1658814: Make node_access() language-aware, but I'm unconvinced that any site would really want to use language-specific node access, especially given its costs in overhead, complexity and maintenance. It's hard enough to keep track of which users have access to which nodes; who would want to multiply that burden and do this per language? and what for?

Sorry if this has been discussed before, but at the point where we are right now, it makes sense to take a step back and reconsider what we're trying to achieve.

I don't want us to decide for the site builder up front, what is going to alienate their users. As said above, this would be needed for a compatible pure workflow to not stump on per language published states. It could also be useful for other things, but my main goal was to make workflow make sense on this level if needed. It is fine if you don't see value in implementing this, the goal was obviously to be able to ignore this if not needed.

As said, if we don't have this, we make multilingual sites needing to pick from per language publication states (which is a very core workflow requirement for sites being translated) and implementing node access. You argue this would be the best we should do. Looks like we might not have resources anyway to do anything else.

agentrickard’s picture

Gabor-

The problem I ran into was that the old model of "Content Translation", wherein a translation of a node is, in fact, a new node, is something we want to discard. However, last I checked, there was no UI and no test framework for running checks for entity-based translations. That was the status we discussed in Denver (see comments 26-29 above).

If that framework is in place, then we have a query / data storage issue that we should be able to solve. But if we can't test entity translation using core UIs, then this can't move forward.

Gábor Hojtsy’s picture

No, we do not currently have a UI. We have extensive test coverage for creating multilingual entities, such as in the search tests. There are APIs to create these, and testing access through the API should in fact be enough / identical in terms of feature set, so I don't see a reason for this to wait on an actual UI to land. As for an actual entity translation UI, there is a patch at #1188388: Entity translation UI in core, but as said, we should not need it to do any progress here.

The node access system is about assigning a boolean to a combination of a number and an operation string and this issue proposed to make that combination include a language code as well (optionally).

agentrickard’s picture

OK. This would unblock me. I'm traveling this week but hope to take a look this weekend. At the least, I can make some time at BadCamp to try to knock this out.

agentrickard’s picture

Assigned: Schnitzel » agentrickard
Status: Needs work » Needs review
FileSize
14.55 KB

Straight re-roll of #34 to get moving again.

agentrickard’s picture

Have to rename the update hook.

Status: Needs review » Needs work

The last submitted patch, drupal-1658846-node_access_grants-55.patch, failed testing.

agentrickard’s picture

The idea of 'fallback' needs serious consideration. Now that we have per-field translation, the access system doesn't quite know what to do with each field translation.

In my tests, the only workaround is as follows:

* Make the hook_node_access_records() return value sensitive to all active translations, using $node->getTranslationLanguages()
* Check for "fallback" status in the cases where:
** The node type is not translatable.
** The node langcode is "multiple"
** The node langcode is "not applicable"
** The node langcode is "not specified"

Here's the fugly code for that check:

/**
 * Determines if a translation should be the default for access.
 *
 * @param Drupal\node\Node $node
 *   The $node being written to. All that is necessary is that it contains a
 *   nid.
 * @param array $grant
 *   A single grant array taken from hook_node_access_records().
 *
 * @return Boolean
 *   TRUE if this node is default for all languages.
 */
function node_grant_is_fallback(Node $node, $grant) {
  // The node must be enabled for translation.
  if (!translation_entity_enabled('node', $node->type)) {
    return TRUE;
  }
  elseif ($node->langcode == LANGUAGE_NOT_APPLICABLE) {
    return TRUE;
  }
  elseif ($node->langcode == LANGUAGE_NOT_SPECIFIED) {
    return TRUE;
  }
  elseif ($node->langcode == LANGUAGE_MULTIPLE) {
    return TRUE;
  }
  return FALSE;
}
Gábor Hojtsy’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, drupal-1658846-node_access_grants-57.patch, failed testing.

Schnitzel’s picture

as discussed with xjm and Gabor at badcamp: I will update my patch to provide a new test node access module which is language aware to not change the current existing test node access module as most of the contrib node access module will not be language aware.

xjm’s picture

So I think we need to add test coverage for various combinations of languages (nodes that have no language, or that have one or multiple translations with different default languages) with:

  • A node access module that ignores the language key and lets node take care of writing the grants.
  • A node access module that writes specific grants per language per node.
  • The combination of the two.

@Gábor Hojtsy and @Schnitzel and I talked about this awhile and we concluded that it's not possible to use grant realms to implement this functionality, but not for the reasons described in #42. While it would be possible to define grant realms per language and write grant records per language, it would only solve the problem of allowing a role granted the realm (e.g.) translator_de to edit any node that has a German translation, not the problem of allowing that role access to edit only the German translation of that node (and not the French).

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
19.14 KB
32.11 KB

Implemented everything as discussed with @xjm and @gabor:
- fixed wrong node_access Table Primary key and added it with langcode column
- added code that node_access() function does correctly load the langcode from the content negotiation
- added new node_access_test_language.module which is a new node_access module which is language aware, so it creates different grants for each language the node is translated to
- new Test NodeAccessLanguageAwareTest.php which tests node access with node_access_test_language.module

Status: Needs review » Needs work
Issue tags: -Needs tests, -Entity system, -Node access, -D8MI, -language-content

The last submitted patch, interdiff-55-62.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review

#62: interdiff-55-62.patch queued for re-testing.

Schnitzel’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests, +Entity system, +Node access, +D8MI, +language-content

The last submitted patch, interdiff-55-62.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
52.7 KB
28.56 KB

checked with @xjm and decided that we will also have to write some combined tests, so where a non language aware module and a language aware node_access module is installed, did this in NodeAccessLanguageAwareCombinationTest.
Also updated NodeAccessLanguageTest to test when actually a private node is created.
And also updated NodeAccessLanguageTest with tests when a node is created without a language defined (so as when no language module is installed).

Status: Needs review » Needs work

The last submitted patch, drupal-1658846-node_access_grants-67.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
52.7 KB
29.01 KB

oh, node install has changed.

YesCT’s picture

adding tag for needs issue summary since this seems to be pretty involved at this point. :)

Gábor Hojtsy’s picture

Issue tags: +sprint
Gábor Hojtsy’s picture

Created this figure for layout out the changes. Updating issue summary with explanation.

Gábor Hojtsy’s picture

Gábor Hojtsy’s picture

Category: task » feature
Priority: Normal » Major
xjm’s picture

Assigned: agentrickard » xjm

Rerolling to clean up some spinach. :)

xjm’s picture

Assigned: xjm » Unassigned
FileSize
17.58 KB
55.11 KB

@GaborHojtsy and @Schnitzel took the time to go over this issue with me in detail during BADCamp, and I'm fairly confident now that this is a good solution given the current node access implementation. The updated summary explains it pretty clearly.

The attached reroll adds the following fixes:

  1. +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
    @@ -0,0 +1,365 @@
    +  /**
    +   * Asserts node_access correctly grants or denies access.
    +   */
    +  function assertNodeAccess($ops, $node, $account, $langcode = NULL) {
    +    foreach ($ops as $op => $result) {
    +      $msg = t("node_access returns @result with operation '@op', language code @langcode.", array('@result' => $result ? 'true' : 'false', '@op' => $op, '@langcode' => !empty($langcode) ? "'$langcode'" : 'empty'));
    +      $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg);
    +    }
    +  }
    

    This method is repeated in all three tests classes, so I've moved it into the base class.

  2. +++ b/core/modules/node/node.moduleundefined
    @@ -2771,8 +2771,24 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
    -  if (empty($langcode)) {
    -    $langcode = (is_object($node) && isset($node->nid)) ? $node->langcode : '';
    +  if (empty($langcode) && (is_object($node) && isset($node->nid))) {
    +    if (module_exists('language')) {
    +      // Load language from content negotiation.
    +      $content_negotiation_langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
    +      // Load languages the node exists in.
    +      $node_translations = $node->getTranslationLanguages();
    +      // If the node does not exist in the language from content negotiation
    +      // return the default language of the node.
    +      if (isset($node_translations[$content_negotiation_langcode])) {
    +        $langcode = $content_negotiation_langcode;
    +      } else {
    +        $langcode = $node->langcode;
    +      }
    +    } else {
    +      $langcode = $node->langcode;
    +    }
    +  } else if (empty($langcode)){
    +    $langcode = '';
    

    The logic here was a bit convoluted, so I've rewritten it somewhat (see interdiff).

  3. I clarified several parts of the patch's documentation.
  4. There was also some missing required documentation (docblocks and parameter documentation) that I've added, as well as some nonstandard one-line summaries (which I've fixed). Summaries should be only one line of 80 characters or fewer, starting with a third-person verb.

The test coverage is excellent. One thing we might want to do is simplify the new node access test module a bit so that it doesn't rely on a field and a magic variable_set() flag. (The new module is modeled on the existing node_access_test.module, which could use its own cleanup anyway. As I recall, I started to do that once and ended up spending four hours just documenting how it works. Maybe a followup.)

I'll take another look at this patch tomorrow; been staring at it too long today. :)

Status: Needs review » Needs work

The last submitted patch, node-1658846-76.patch, failed testing.

xjm’s picture

Status: Needs work » Needs review
FileSize
1.03 KB
56.14 KB

Missed one.

xjm’s picture

Couple other cleanups I missed:

Status: Needs review » Needs work

The last submitted patch, node-1658846-78.patch, failed testing.

xjm’s picture

Hm, the same fails from #78 occur with #69 rebased. Either something went funky with the rebase, or an intermediate commit has broken this patch.

xjm’s picture

This is some combination of "Ask and ye shall receive" and "be careful what you wish for": #1814402: Convert 'node_access_test_private' variable to state() system

xjm’s picture

Status: Needs work » Needs review
FileSize
632 bytes
56.14 KB

Status: Needs review » Needs work

The last submitted patch, node-1658846-81.patch, failed testing.

xjm’s picture

Status: Needs work » Needs review
FileSize
632 bytes
56.14 KB
xjm’s picture

FileSize
36.72 KB
56.21 KB

Some search-and-replace cleanups (adding articles and periods, fixing capitalization, removing t() from assertion message texts, etc.).

agentrickard’s picture

  • Does the update hook need a call to node access rebuild or at least to flag for rebuild?
  • Why are we putting the pressure on implementing modules and not the API to run the $node->getTranslationLanguages() loop? Shouldn't that be in the storage API issue.
xjm’s picture

Why are we putting the pressure on implementing modules and not the API to run the $node->getTranslationLanguages() loop? Shouldn't that be in the storage API issue.

I'm confused by this comment. In this patch, the API takes care of it. Modules don't need to unless they want to. The fact that the new module here does is just part of its dummy implementation; the existing node access test module is unchanged, demonstrating that non-language-aware node access modules can just ignore it.

Edit: Maybe we need to change the test module so we have better coverage for the fallback functionality (and avoid this confusion).

xjm’s picture

Does the update hook need a call to node access rebuild or at least to flag for rebuild?

Yes probably. I thought it did but I guess not.

xjm’s picture

Assigned: Unassigned » xjm

Refactoring the tests a bit.

agentrickard’s picture

I don't see that part of the API in the patch. I would expect to see a loop in node_access_acquire_grants() or _node_access_write_grants(), but I don't see one.

I think my confusion is that I expect all node access modules to be language aware. But I think what you're saying is that if the $grants don't specify a language, then we just assert a fallback and default langcode. This was one of the issues we discussed at BadCamp, and I think you have solved this problem.

xjm’s picture

Yep, the default value for the fallback column is 1, so things node access records are a fallback unless they aren't.

xjm’s picture

FileSize
51.63 KB

I simplified the test module significantly and refactored the tests for clarity. Not bothering with an interdiff because pretty much the whole tests have changed, though the same assertions are present.

A couple of the assertions demonstrate behavior I'm not sure about. I'll follow up more later.

Edit: #89 also still needs to be addressed.

xjm’s picture

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,267 @@
+    // If the node is marked public, any existing translations should be
+    // accessible, regardless of whether they are marked private. Access should
+    // only be denied when a translation that does not exist is specifically
+    // requested.
+    // With the Hungarian translation marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'en');

So this is the part that seems suspect to me. In this case, the comment actually contradicts the assertions below it. (It's the same in the earlier versions of the patch.) The normal behavior for node access would be additive, such that when the non-language-aware node access module grants access without specifying a langcode, access is granted. Here, the fact that the langcode is not specified is resulting in language-aware node access modules always overriding non-language-aware ones, regardless of circumstance. (The node access query tag shows the same behavior.) This seems like a bug to me.

Gábor Hojtsy’s picture

@xjm: That seems to be due to the non-language aware node access saving only one fallback entry while the language aware node access saving entries for each language, so it would obviously trump the fallback-only entry. Do you think it would make sense for the non-language-aware entry to be saved for all languages applicable to the node then?

xjm’s picture

Yeah, I think that's what @agentrickard was getting at in #91 and at BADCamp. If a particular record in hook_node_access_records() does not specify a language code, I think we can assume it applies to all translations. So we'd need to either save a record for each language when no specific language is specified, or alter the logic in both node_access() and node_query_node_access_alter() somehow such that both records get selected.

I'd also like to add some additional coverage to see what happens when a language-neutral node is saved with one or both of the access control modules active.

Gábor Hojtsy’s picture

I think writing all records in that case would be better, so that we clearly have all records on the same level as intended instead of some records being "more equal" than others.

agentrickard’s picture

That sounds like the loop I was talking about, and the query parameters I think I added in my last patch.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

All, right, looks like that is the direction then, in short: save records in all language codes if none was provided (instead of the current behaviour of saving only one in the original language code). @Schnitzel can you help implement that so we can do a quick round of reviews and then get this in? :)

Schnitzel’s picture

Assigned: xjm » Schnitzel

yes, will do!

Schnitzel’s picture

So I tried to change it, but actually I'm more confused then before ^^

I agree that this:

    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user);
    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'hu');
    $this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private'], $this->web_user, 'ca');
    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'en');

looks confusing, especially because of this:

    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user);
    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user, 'hu');
    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'ca');
    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'en');

Short info:

$this->nodes['public_hu_private'] has this definitions:
- non-language aware node_access module: public
- language aware node_access module: hu private, ca: public
- Default language: hu <-- important!

$this->nodes['public_ca_private'] has this definitions:
- non-language aware node_access module: public
- language aware node_access module: hu public, ca: private
- Default language: hu <-- important!

So in the first case:
$this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user);
is denied for no language code, because the default language (which will be used as fallback) is denied.

in the second case:
$this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user);
is granted for no language code, because the default language is granted and this is used for as fallback.

So one thing we figured out during the D8MI Meeting in IRC today: the non-language aware node access module creates grantes only when the node is specifically private. If it is set to public then it returns no grants and depends on the Core behavior of creating an "all 1 1 1" grant. Whereas the language aware node access module creates grants for the node no mater if the node itself actually has one translation private.

So I could imagine three things:

  1. Change the non-language aware node access module to return grantes even the node is public
    --> this would be a change from the current behavior so not the right thing to do
  2. Change the language aware node access module to return grantes only if there is actually a translation for the current node which is public
    -->this would then fit with the non-language aware node access module but would not change something from the cases on top
  3. Change the Core to create grant entries for each translation of the node, when some of them are missing in the passed in grants
    -->this could maybe be a good case and actually also my opinion, BUT: It would not change any of the cases below, the first case: $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user); would still be denied access because there is an grant for this.

so you see I don't really see a case where changing this:

save records in all language codes if none was provided

would actually make a difference? :/

Gábor Hojtsy’s picture

Change the non-language aware node access module to return grantes even the node is public
--> this would be a change from the current behavior so not the right thing to do

I don't think it is safe to assume in a node access module that if there are no grants returned, then the node will be accessible. What is safe to assume is that the module is hands-off in terms of that node and other modules decide. The tests should work with that approach as well, if not already.

Change the language aware node access module to return grantes only if there is actually a translation for the current node which is public
-->this would then fit with the non-language aware node access module but would not change something from the cases on top

A node access module should return grants based on its thinking of access. If it wants to grant or revoke access specifically, it should return so. It should not assume any other environmental setup :)

Schnitzel’s picture

okay, so for me this sounds like, these two things are fine, but should we now still change the core behavior to: "save records in all language codes if none was provided" ?

salvis’s picture

should we now still change the core behavior to: "save records in all language codes if none was provided" ?

Doubling the number of records if any language (besides English) is installed is not so great, especially because it's unclear what percentage of sites really need language-specific node access.

Say you want to run a site in German — you're automatically punished, even though you have no English content and you couldn't care less about language-specific node access.

Can we find a better solution?

Gábor Hojtsy’s picture

@salvis: the proposal is to add records in all languages that the node is available in. So if you are on a German only site, that will not have English records. Also, in Drupal 8, you can remove English, so you'd only have German as a configured language, so the situation is even better :)

salvis’s picture

In the absence of complete and sensible UI translations, removing English is probably not a viable option, but it's a nice idea.

What about LANGUAGE_ALL, LANGUAGE_DEFAULT, LANGUAGE_NOT_APPLICABLE, LANGUAGE_NOT_SPECIFIED, LANGUAGE_SITE_DEFAULT?

Gábor Hojtsy’s picture

@salvis: you can remove English even though the missing translations would show up as English. I'd avoid further discussing this unrelated issue here. Let's focus on getting this change in!

salvis’s picture

@Gábor Hojtsy:

I'd avoid further discussing this unrelated issue here.

Yes, definitely, I didn't intend to do that.

Have you considered the special language codes? Even if we have nothing but German, we probably need a way to answer queries with LANGUAGE_ALL, for example. And if we have content with LANGUAGE_NOT_APPLICABLE, we need to answer to queries with 'de'.

Gábor Hojtsy’s picture

@salvis: I believe that is already possible.

Gábor Hojtsy’s picture

Back on topic, the work that seems to be left is that if no langcode is provided with a grant, core would save the grant for all langcodes applicable to the node not just the original langcode for the node. @Schnitzel: can you still help with doing that?

YesCT’s picture

Gábor Hojtsy’s picture

Assigned: Schnitzel » Unassigned

Needs someone to implement the final pieces, we are very close. Need a volunteer!

plach’s picture

I can work on this in a few days if none wants to pick this up before.

Gábor Hojtsy’s picture

@plach/@Schnitzel: any possibility that you might be available or I should go look for others. It feels pretty awkward we got this close to implementing this and then stop before the (hopeful) finish line :/

plach’s picture

Sorry, I don't think I'll be able to work on this earlier than a week. If someone else wishes to step-up feel free.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
51.62 KB

Rerolled patch from #94 "as is".

penyaskito’s picture

Add a grant for each language this node can be translated to if a specific language is not given in grants.

Status: Needs review » Needs work

The last submitted patch, node-1658846-118.patch, failed testing.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
52.94 KB
1.32 KB

Quoting #111:

Back on topic, the work that seems to be left is that if no langcode is provided with a grant, core would save the grant for all langcodes applicable to the node not just the original langcode for the node.

If I understood, we want to add every langcode this node can be translated to.
My previous patch added a reference to translation_entity_enabled in _node_access_write_grants and this is not a valid call, because translation_entity could be disabled.

So maybe this functionality should be moved to translation_entity by implementing hook_node_access_records_alter and adding a new grant for each available language. Is this true of I'm missing the point?

Added a patch for showing my view, but I'm sure that more simpletests should be implemented if this is the right path to follow.
Interdiff is with #117, not #118.

Status: Needs review » Needs work

The last submitted patch, node-1658846-120.patch, failed testing.

Schnitzel’s picture

sorry for my way too long non answering :/
But unfortunately everybody needs something from me right now, as I only have time to write at 0100...

But yes @penyaskito the idea is, that core adds a grant for each language of a node where any other module does not create a grant.
A really good point is what you mentioned in #120, but actually getTranslationLanguages() is a function of Entity and not of entity_translation. So why not just use getTranslationLanguages() and never check translation_entity_enabled()?
Because Multilanguage is something which Entity is capable of, Entity Translation only enables you better handling of this Multilanguage system.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
52.27 KB
2.25 KB

Thanks for the feedback @Schnitzel!
Attached patch should work then, let's see if its back to green.

Status: Needs review » Needs work

The last submitted patch, node-1658846-123.patch, failed testing.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
2.32 KB
52.33 KB

Oops, failures based on using the language object instead of langcode.

Status: Needs review » Needs work

The last submitted patch, node-1658846-125.patch, failed testing.

penyaskito’s picture

Assigned: Unassigned » penyaskito

We should check that the entry for that node-grant-language does not already exist, or we get duplicate key errors as in those tests.

I'll work on it ASAP if no-one tries it before.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
52.6 KB
1.96 KB

Added check and reworded comment.

Status: Needs review » Needs work

The last submitted patch, node-1658846-128.patch, failed testing.

Schnitzel’s picture

mhh wait, why can it even happen that there are two grants for the same nodeID and the same translation in there?
All Grants for a Node should be deleted before the new Grants are created, so this should no happen. I think there is something else wrong with the array merge or so.

Gábor Hojtsy’s picture

Category: feature » task
Issue tags: +epic

Refiling as a task given not solving this would mean a regression in terms of content access features (due to entity translation taking place of node-copy translation).

Also agreed with Schnitzel on the duplication question.

Gábor Hojtsy’s picture

@penyaskito: any chance you could take a look at this soon? It would be pretty important to get this in sooner than later.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
52.22 KB
2.05 KB

New patch for moving forward.

I were looping over the languages without looping in the grants first. Locally I have some test errors, but they look unrelated. Let's what the testbot has to say.

penyaskito’s picture

Removed forgotten debug statement.

Gábor Hojtsy’s picture

Reviewed, I don't see anything wrong with this patch, asked @xjm and @Schnitzel to take a look.

xjm’s picture

Thanks @penyaskito. That looks like the solution we were discussing.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,267 @@
+    // If the node is marked public, any existing translations should be
+    // accessible, regardless of whether they are marked private. Access should
+    // only be denied when a translation that does not exist is specifically
+    // requested.
+    // With the Hungarian translation marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'en');
+
+    // With the Catalan translation marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'en');
+
+    // With both translations marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'en');

The comment here still contradicts the assertions in the tests. I expected some of these assertions to change as part of the "write grants for all languages" change. It might just be a result of the existing test node access module having unintuitive behavior, as @Schnitzel points out in #102. (I'd almost consider that a bug in that module.)

@Gábor Hojtsy is definitely right that the language access should not make any assumptions about what other modules do, but the result is that we're missing test coverage for the case where there are actually explicit "public" grant records written from another module, because the existing test module doesn't do that. The solution to this problem is to either add another test module, or to change the behavior of the existing module. That's a lot of overhead for this already-gnarly issue, so I'm inclined change the comment so it is at least accurate for the current behavior, smack on a @todo, add more coverage in a followup, and open a new bug if it turns out there's a bug for that case.

I'd also like to add some additional coverage to see what happens when a language-neutral node is saved with one or both of the access control modules active.

We also still need this test coverage, I think. This scenario seems more likely to me than the complicated situation above, so I'm less comfortable with deferring that to a followup, but please let me know if you feel differently.

+++ b/core/modules/node/node.moduleundefined
@@ -2788,9 +2805,13 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
       $query = db_select('node_access');
       $query->addExpression('1');
       $query->condition('grant_' . $op, 1, '>=');
-      $nids = db_or()->condition('nid', $node->nid);
+      $nids = db_and()
+        ->condition('nid', $node->nid)
+        ->condition('langcode', $langcode);
       if ($node->status) {
-        $nids->condition('nid', 0);
+        $nids = db_or()
+          ->condition($nids)
+          ->condition('nid', 0);
       }
       $query->condition($nids);
       $query->range(0, 1);
@@ -3037,6 +3058,9 @@ function node_query_node_access_alter(AlterableInterface $query) {

@@ -3037,6 +3058,9 @@ function node_query_node_access_alter(AlterableInterface $query) {
   if (!$op = $query->getMetaData('op')) {
     $op = 'view';
   }
+  if (!$langcode = $query->getMetaData('langcode')) {
+    $langcode = FALSE;
+  }
 
   // If $account can bypass node access, or there are no node access modules,
   // or the operation is 'view' and the $account has a global view grant
@@ -3100,6 +3124,13 @@ function node_query_node_access_alter(AlterableInterface $query) {

@@ -3100,6 +3124,13 @@ function node_query_node_access_alter(AlterableInterface $query) {
         $subquery->condition($grant_conditions);
       }
       $subquery->condition('na.grant_' . $op, 1, '>=');
+      if ($langcode === FALSE) {
+        $subquery->condition('na.fallback', 1, '=');
+      }
+      else {
+        $subquery->condition('na.langcode', $langcode, '=');
+      }

Finally, I should have brought this up previously, but there's a lot of obscure, weird query logic here and no inline comments explaining it. (It's a pre-existing problem, but it becomes worse now that the query and query alter are more complicated.) Can we add some inline comments explaining what's going on in these two functions?

Thanks everyone for your perseverance on this issue. Node access is complicated...

penyaskito’s picture

I'm not a node grants expert, so I don't fully understand yet what is needed.

Pending (from my understanding):

1. Change assertions from NodeAccessLanguageAwareCombinationTest or improve comments.

2. Add more coverage for the case where there are actually explicit "public" grant records written from another module or write a follow-up issue.

3. Add coverage to see what happens when a language-neutral node is saved with one or both of the access control modules active.

4. Document obscure, weird query logic in node access checks.

This patch only covers 4) since I need more time for understanding the other points (I'll ping @GaborHojtsy or @xjm in IRC this week if needed).

Status: Needs review » Needs work

The last submitted patch, node-1658846-137.patch, failed testing.

vijaycs85’s picture

Status: Needs work » Needs review
FileSize
52.58 KB

Re-rolling...

Gábor Hojtsy’s picture

Status: Needs review » Needs work

@penyaskito: I think (1) would need a documentation update indeed, the code comments do seem to contradict the test as pointed out by @xjm. For (2) I think adding yet another test module to test combinations with public grants would be good in a followup and agree (3) would need test coverage (not sure that would need to be here too, but I agree it is closer to introducing the multilingual functionality) . The added comments for (4) look good but none of them wrap at 80 chars. Please wrap them.

Gábor Hojtsy’s picture

@penyaskito: can you work on fixing these points?

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests, -Entity system, -Node access, -D8MI, -sprint, -language-content, -translation editorial workflow, -epic

#139: 1658846-language-node-139.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +Needs tests, +Entity system, +Node access, +D8MI, +sprint, +language-content, +translation editorial workflow, +epic

The last submitted patch, 1658846-language-node-139.patch, failed testing.

vijaycs85’s picture

Status: Needs work » Needs review
FileSize
1.53 KB
53.11 KB

Re-rolling with current code base and comment wrapping for (4).

penyaskito’s picture

Schnitzel’s picture

w0000t Schnitzel is back!
I'm super sorry for not taking care about this :/
unfortunately I was super busy during Nov-Jan to finish customer projects and letting my employees into holidays, but I finally found time to check my node access issue again!

So I read through all the things still open:

1. Change assertions from NodeAccessLanguageAwareCombinationTest or improve comments.

I think @penyaskito did a good Job with this in #145, so nothing to do here.

2. Add more coverage for the case where there are actually explicit "public" grant records written from another module or write a follow-up issue.

Fully agree, but as this is not yet covered currently I think we can make a follow up Issue: "Refactor Core Node Access Tests to not only test for denies, also test for public grants"

3. Add coverage to see what happens when a language-neutral node is saved with one or both of the access control modules active.

Done! I added more nodes to both Tests: "Language Aware" and "Lanuage Aware Combination", as there where already tests for language-neutral (eq: no specific language").

4. Document obscure, weird query logic in node access checks.

Checked @penyaskito work here, rewrote it a bit and also fixed the 80chars length issue.

So from my point of view this is ready to go, and we need a follow up.

What do you think @xjm @gabor?

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Changes look good to me, fix @xjm's concerns :)

penyaskito’s picture

LGTM if a second pair of eyes are needed :-)

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,325 @@
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and no specific langcode.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // All nodes are returned.
+    $this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and langcode de.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('langcode', 'de')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');

Where's user 1 set up? I don't see any difference here from the assertions above in terms of which user is current during the queries - also isn't it possible to use addMetaData() to specify the account, that'd be more explicit.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,325 @@
+    // Even though there is no German translation, all nodes are returned
+    // because node access filtering does not occr when the user is user 1.
+    $this->assertEqual(count($nids), 10, 'db_select() returns all nodes.');

s/occr/occur.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
@@ -0,0 +1,265 @@
+  function setUp() {
+    parent::setUp();
+
+    node_access_rebuild();

Is that really necessary to rebuild node access, wouldn't it happen on module_enable() anyway?

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
@@ -0,0 +1,265 @@
+    // Clear permissions for authenticated users.
+    db_delete('role_permission')
+      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+      ->execute();

This too - what default permissions are messing up the test?

+++ b/core/modules/node/node.api.phpundefined
@@ -271,8 +275,9 @@ function hook_node_access_records(Drupal\node\Node $node) {
-    // Only published nodes should be viewable to all users. If we allow access
-    // blindly here, then all users could view an unpublished node.
+    // Only published Catalan translations of private nodes should be viewable
+    // to all users. If we fail to check $node->status, all users would be able

This changes the meaning of the comment quite a bit, I'd add a new hunk I think?

+++ b/core/modules/node/node.moduleundefined
@@ -2604,7 +2604,24 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
@@ -2649,10 +2666,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {

@@ -2649,10 +2666,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
     if (module_implements('node_grants')) {
       $query = db_select('node_access');
       $query->addExpression('1');
+      // Only interested for granting in the current operation.
       $query->condition('grant_' . $op, 1, '>=');
-      $nids = db_or()->condition('nid', $node->nid);
+      // Check for grants for this node and the correct langcode.
+      $nids = db_and()
+        ->condition('nid', $node->nid)
+        ->condition('langcode', $langcode);
+      // If node is published also take the default grant into account, the
+      // default is saved with nid = 0.
       if ($node->status) {
-        $nids->condition('nid', 0);
+        $nids = db_or()
+          ->condition($nids)
+          ->condition('nid', 0);
       }
       $query->condition($nids);
       $query->range(0, 1);
@@ -2907,6 +2932,9 @@ function node_query_node_access_alter(AlterableInterface $query) {

@@ -2907,6 +2932,9 @@ function node_query_node_access_alter(AlterableInterface $query) {
   if (!$op = $query->getMetaData('op')) {
     $op = 'view';
   }
+  if (!$langcode = $query->getMetaData('langcode')) {
+    $langcode = FALSE;

Have any explains been run on this?

Also why change the query regardless of whether multiple languages are enabled?

At first glance this looks like a lot of overhead for sites that may never use this feature, it ought to be possible to only add that if it's likely to be used - i.e. if there's more than one language on the site. Alternatively ET module could alter the query?

Gábor Hojtsy’s picture

@catch: only responding to the query building question since that seems to be a major item. I think how that should work depends on what indexes are available. The patch changes this only on indexes:

-    'primary key' => array('nid', 'gid', 'realm'),
+    'primary key' => array('nid', 'gid', 'langcode', 'realm'),

There are likely easy to find people who know better about database performance, but I believe if we query for parts of the index that will only work performantly if the query and the index are set up properly (ie. we use the first part of the index?). So if there is no proper index for the language-less case then querying for that might not end up being better. However if the site only has one language then I'd assume the database optimises out the langcode since there is no variance.

catch’s picture

Realm is always added to the query - at least if the user has any grants, so possibly language could be put after in key order? Or add another index?

Schnitzel’s picture

Thanks @catch for the review!

Here a new Version of the Patch, Changelog:

  • The node_access_rebuild(); is necessary, as this does not happen during module_enable
  • Specifically added a the user1 admin user for the db_selects(). Actually it is run with user 1 per default, but it makes more sense now
  • The db_delete('role_permission') is really not necessary. I copied it from the already existing core node access test. Removed it now in all places. Lets see if testbot likes this :)
  • Changed the order of the primary keys from ('nid', 'gid', 'langcode', 'realm') to ('nid', 'gid', 'realm', 'langcode')

Here some EXPLAIN on the db_select(), looks good to me? If this needs more performance testing, I'm happy to explain a person which knows more about performance what the whole thing does.

Current Drupal 8 Core:

SQL query: EXPLAIN EXTENDED SELECT 1 AS expression FROM node_access node_access WHERE (grant_view >= '1') AND(( (nid = '1') AND (langcode = 'en') )OR (nid = '0') )AND(( (gid = '0') AND (realm = 'all') )OR( (gid = '0') AND (realm = 'node_access_test_author') )OR( (gid = '0') AND (realm = 'node_access_all') )) LIMIT 1 OFFSET 0; 
Rows: 1

id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	node_access	range	PRIMARY	PRIMARY	813	NULL	4	100.00	Using where

With Patch:

SQL query: EXPLAIN EXTENDED SELECT 1 AS expression FROM node_access node_access WHERE (grant_view >= '1') AND(( (nid = '1') AND (langcode = 'en') )OR (nid = '0') )AND(( (gid = '0') AND (realm = 'all') )OR( (gid = '0') AND (realm = 'node_access_test_author') )OR( (gid = '0') AND (realm = 'node_access_all') )) LIMIT 1 OFFSET 0; 
Rows: 1

id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	node_access	range	PRIMARY	PRIMARY	813	NULL	4	100.00	Using where
Schnitzel’s picture

Status: Needs work » Needs review
Gábor Hojtsy’s picture

@Schnitzel, thanks! What about the last concern raised by @catch? That is where the order of indexes is significant.

Schnitzel’s picture

@gabor

I changed the order of the keys, but honestely I don't know if this really fixes what catch ment. Any advises here?

Gábor Hojtsy’s picture

Well, the question/suggestion was to not add any langauge condition if there is definitely no language in node access. What do you think?

Gábor Hojtsy’s picture

@catch/@Schnitzel: I think tying the node permissions hook extension to entity translation being enabled might be a mistake. While entity translation provides a user interface to configure and enter translations, it is possible to disable the module and still view the entity translations (entity display controllers are independent of data entry). So I can imagine people on live sites might disable the module (or even use a separate module to enter translation if they wish so), which would otherwise be perfectly valid in the system as-is now. I've asked @YesCT to try and reproduce the independence of display vs. entry.

YesCT’s picture

Assigned: Unassigned » penyaskito
Status: Reviewed & tested by the community » Needs review

re the possibility of having ET UI modify the query, mentioned by @catch in #149:

Gabor suggested testing to see if content displays in the translated languages without et enabled.

Tested (with and without the patch, behavior the same) by:

  1. enable ET (content translation module)
  2. add a couple languages (af, de)
  3. create a node
  4. translate node
  5. verify /node/1 and /de/node/1 display their respective translated content (use language switcher block if desired)
  6. disable ET
  7. verify again /node/1 and /de/node/1 show the translated content

so, putting the query in ET would not be advised.

[edit, added below]
maybe not common, but that situation might happen if content is translated not on the live site.

Possible for deployment, where you want to optimise performance on live and not edit content directly there.

Gábor Hojtsy’s picture

Assigned: penyaskito » Unassigned
Status: Needs review » Reviewed & tested by the community

Sounds like the above resolves @catch's concerns then and demonstrates that entity translation module is not the right place to put a query alter for this.

catch’s picture

Assigned: penyaskito » Unassigned
Status: Needs review » Needs work

I don't think the query alter running when Entity Translation module is disabled is a good reason to bake this into node module, that's an extreme edge case vs. additional complexity for sites that will never use this at all.

YesCT’s picture

I'm not clear on the concern.

What about this situation:
No ET ever enabled, just languages.

Some content in some languages, some content in other languages. Would this be useful in that case? [edit: or maybe that case is covered without this patch]

Perhaps the issue summary needs some more explanation.

Gábor Hojtsy’s picture

@catch: so in short, the display logic and fallback is within the entity system, entry and configuration is with the entity translation module. If the node permissions query altering (display/entry access logic) happens in the module, then if I create nodes with varying node access values for translations, then disable the module, I also possibly leak access to those translations, since the translations will be there but the code will not be there anymore to limit access. You are proposing we make it work like this and ignore the unauthorized access possibility that ensues?

catch’s picture

I'd be OK with language module holding the query alter.

Gábor Hojtsy’s picture

All right, let's alter that in from language module then (I assume if there are multiple user entered languages, because otherwise this is really an optimization only for English sites, not for single language foreign language sites).

xjm’s picture

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -46,13 +46,9 @@ public static function getInfo() {
-    // Clear permissions for authenticated users.
-    db_delete('role_permission')
-      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
-      ->execute();

I think this hunk was mostly there to ensure there weren't any false positives down the line.

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
59.45 KB
2.28 KB

spoke with @xjm and @catch about the performance issue in node_query_node_access_alter(), moved now the langcode check into language module, as it really makes only sense here.

Schnitzel’s picture

oh, btw not 100% if the naming "node_access_tag_subquery" for the alter is correct.
I thought about node_access_subquery first, but actually in node_access() there is no subquery, and the alter is called in node_query_node_access_alter() which is the node_access TAG function. If anybody has a better idea, I'm open to change it ;)

Schnitzel’s picture

Issue tags: +Needs documentation

new alter, so needs documentation in case this is how we want to do it.

xjm’s picture

First of all, I checked with @webchick and she agrees that this is appropriate as a task (meaning, not subject to the Feb. 18 deadline).

Moving this alter into language.module makes more sense to me, and actually helps address a concern I've had for awhile about the node module including all this functionality itself. However, now we have support for this functionality split between Node and Language. I'm wondering if it would make more sense to move more of the functionality to the Language module?

I think we can drop the word "tag" from the tag name; it's a bit redundant. node_access_subquery probably works, but there might be something that better describes its purpose.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
@@ -0,0 +1,266 @@
+    // Create four Hungarian nodes with Catalan translations:
+    // 1. One with neither language marked as private.
+    // 2. One with only the Hungarian translation private.
+    // 3. One with only the Catalan translation private.
+    // 4. One with both the Hungarian and Catalan translations private.

This comment needs to be updated now that there are six nodes.

+++ b/core/modules/node/node.api.phpundefined
@@ -234,11 +234,16 @@ function hook_node_grants($account, $op) {
+ * - langcode: (optional) The language code of a specific translation of the
+ *   node, if any. Modules may add this key to grant different access to
+ *   different translations of a node, such that (e.g.) a particular group
+ *   is granted access to edit the Catalan version of the node, but not the
+ *   Hungarian version. If no value is provided, the langcode is set
+ *   set automatically from the $node parameter and the node's original
+ *   language (if specified) is used as a fallback.

This would probably be the place to add documentation about the subquery alter. Also, do we need to specify here that the functionality is dependent on the Language module?

+++ b/core/modules/node/node.installundefined
@@ -714,6 +728,34 @@ function node_update_8014() {
+ * Add language.langcode and fallback field to node_access table.

{node_access} should be in curlies here.

+++ b/core/modules/node/node.moduleundefined
@@ -2968,10 +2993,13 @@ function node_query_node_access_alter(AlterableInterface $query) {
+      drupal_alter('node_access_tag_subquery', $subquery, $query);

We probably need a comment for this line.

+++ b/core/modules/node/node.moduleundefined
@@ -3041,18 +3066,35 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
+      if (isset($grant['langcode'])) {
+        $grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
+      }
+      else {
+        $grant_languages = $node->getTranslationLanguages(TRUE);
+      }
+      foreach ($grant_languages as $grant_langcode => $grant_language) {

What happens when the Language module is not enabled but someone tries to set a langcode in their hook implementation? Hopefully node access gets rebuilt when language is disabled in this case, and any modules that have langcode-aware node access would specify Language as a dependency? (That also adds credence to this functionality belonging to Language instead of Node.)

There are also a number of comments that could use some clarification. I can help with that once we decide what else (if anything) we want to move over to Language. If we do update the patch, let's also be sure to post the test-only patch alongside the full one to be sure that our tests are still providing the expected coverage. (I'm also still not 100% sold on removing the permissions cleanup.)

Leaving at NR for feedback.

Gábor Hojtsy’s picture

@xjm:

Moving this alter into language.module makes more sense to me, and actually helps address a concern I've had for awhile about the node module including all this functionality itself. However, now we have support for this functionality split between Node and Language. I'm wondering if it would make more sense to move more of the functionality to the Language module?

It would be great to know what do you mean in more concrete terms. In Drupal 8, the direction of moving code was much more from language (locale) module to the individual modules, so the individual modules are language aware and its not language module implementing things on behalf of other modules, to set up a pattern, which contrib modules would need to follow anyway. For example, in Drupal 7, the node language property on nodes, as well as the language filtering on the admin interface was in the node module while the language property UI was in locale module altered in for the node form. That was pretty odd, because the other parts could not be just altered in, so that part of the logic was here and the other was there made it pretty confusing.

I think we now have the same "confusing" setup with altering from language module but otherwise core functionality with language support in node module. It is clear the schema and the needed understanding of per-language access records would need to live in node module because that handles the data storage. So I'm not sure we are cleaning up code and making things easier to work with / understand by separating these logic pieces.

The general philosophy otherwise in Drupal 8 is that language is not a feature that is injected from the outside but natively understood in modules. Eg. search module indexes nodes with multiple languages natively, field API handles multilingual properties natively.

plach’s picture

I completely agree with Gabor: Language as well as ET are providing their features in an entity-type agnostic way. Hence it would be architecturally wrong to force them to add code explictly dealing with a particular entity.

If we want to reduce the overhead for node access on monolingual sites, which is a sensible and desirable goal, we should just do so and have Node natively make its node acess support language-aware when multiple language are available.

catch’s picture

we should just do so and have Node natively make its node acess support language-aware when multiple language are available.

That's also fine with me..

Gábor Hojtsy’s picture

All right, here is an update. It moves back the language condition to the node module as discussed above. Also made all changes suggested by @xjm (apart from moving more things out of node module that is).

1. Moved back the logic and made it conditional on language_multilingual() in the query.
2. No need to document the query alter or name it differently anymore.
3. Updated the comment for the 6 nodes instead of the 4.
4. Updated the comment on langcode in hook_node_access_records() that it should only be provided if multilingual content is possible.
5. Fixed missing {node_access} curlies.
6. Made node access reindex itself also if language module is disabled.

Please review!

Status: Needs review » Needs work

The last submitted patch, language-grants-1658846-173.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
60.27 KB
715 bytes

Bad invisible whitespace...

Status: Needs review » Needs work

The last submitted patch, language-grants-1658846-176.patch, failed testing.

Gábor Hojtsy’s picture

Failed with Drupal\Core\Config\StorageException' with message 'Failed to write configuration file... and friends. Should be a fluke.

Gábor Hojtsy’s picture

Status: Needs work » Needs review

#175: language-grants-1658846-176.patch queued for re-testing.

attiks’s picture

Patch looks good, and lots of tests ++

Some minor comment issues

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,327 @@
+    // translation public:
...
+    // the Catalan nor the English ones:
...
+    // access should be denied in all cases:

':' at the end?

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,327 @@
+    // denies access
...
+    // Access only for request with no language defined
...
+    // deny access
...
+    // denies access

dot missing at the end

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,327 @@
+    // The results should be the same as the for default.

for = four? but assert uses 3

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
@@ -0,0 +1,270 @@
+    // The results should be the same as the for default.

.. as the default.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Module enabled.

Module -> module

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Tests that access provided if requested with no language.

access is granted

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Tests that access not provided if requested with Hungarian language.

access is not granted

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Module enabled.

Module -> module

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Tests that access not provided if requested with no language.
...
+    // Tests that access not provided if requested with Hungarian language.
...
+    // Tests that access not provided if requested with no language.

.. is not granted ...

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
@@ -61,31 +47,196 @@ function testNodeAccess() {
+    // Module enabled.

Module -> module

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.phpundefined
@@ -30,4 +30,35 @@ function setUp() {
+        "node_access() returns @result with operation %op, language code %langcode.",

why " and not '?

penyaskito’s picture

Assigned: Unassigned » penyaskito
Status: Needs review » Needs work
penyaskito’s picture

Assigned: penyaskito » Unassigned
Status: Needs work » Needs review
FileSize
60.41 KB
12.21 KB

Handled @attiks comments.

Gábor Hojtsy’s picture

@attiks, @xjm, @catch, others? Any more concerns?

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC with all concerns resolved from above.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/node/node.moduleundefined
@@ -2642,10 +2659,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
+      // Only interested for granting in the current operation.
       $query->condition('grant_' . $op, 1, '>=');
-      $nids = db_or()->condition('nid', $node->nid);
+      // Check for grants for this node and the correct langcode.
+      $nids = db_and()
+        ->condition('nid', $node->nid)
+        ->condition('langcode', $langcode);
+      // If node is published also take the default grant into account, the
+      // default is saved with nid = 0.
       if ($node->status) {
-        $nids->condition('nid', 0);
+        $nids = db_or()
+          ->condition($nids)
+          ->condition('nid', 0);

Doesn't this also need to be wrapped in a language_multilingual() check? If not I think it could use a comment - I'm not sure from reading the code what happens if langcode is NULL.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

@catch: the code above in the patch initializes the $langcode to the node's language and then further clarifies that to be the translation $langcode of the node for the page's content language if present. So it makes all attempt to get to the proper langcode. I'm not sure we should summarize part of the code of the same function from above. We usually don't do this.

webchick’s picture

Status: Reviewed & tested by the community » Needs review

Moshe wanted to do a review of this prior to commit (and xjm too) so taking out of the RTBC queue for now.

xjm’s picture

Sorry that I let this slide for a month again. (In my defense, there was a vacation and a DrupalCon and a new job and a cold in there.) ;) I reviewed the patch again, and all the recent changes seem correct to me. I withdraw my earlier concerns, and I'm confident this is ready. I'm really satisfied with the robust test coverage and the overall architecture, but it does look like a couple of my earlier questions haven't been addressed.

  1. We agreed earlier:

    2. Add more coverage for the case where there are actually explicit "public" grant records written from another module or write a follow-up issue.

    Fully agree, but as this is not yet covered currently I think we can make a follow up Issue: "Refactor Core Node Access Tests to not only test for denies, also test for public grants"

    Let's file this and link it here.

  2. +++ b/core/modules/node/node.moduleundefined
    @@ -2963,10 +2991,26 @@ function node_query_node_access_alter(AlterableInterface $query) {
    +      drupal_alter('node_access_tag_subquery', $subquery, $query);
    

    "Allow modules to alter..." and the usecase? Also, I think we need to document this alter hook?

  3. +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
    @@ -61,31 +47,196 @@ function testNodeAccess() {
    +    // Load the user 1 user for later use.
    +    $admin_user = user_load(1);
    

    I know that catch asked for this, but why do we need to do it? Maybe putting the why in the comment would be good. Even after all this time I am still hazy on what's up with user 1 in simpletest.

  4. +++ b/core/modules/node/node.moduleundefined
    @@ -3036,18 +3077,35 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
    +      if (isset($grant['langcode'])) {
    +        $grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
    +      }
    +      else {
    +        $grant_languages = $node->getTranslationLanguages(TRUE);
    +      }
    +      foreach ($grant_languages as $grant_langcode => $grant_language) {
    +        // Only write grants; denies are implicit.
    +        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
    +          $grant['nid'] = $node->nid;
    +          $grant['langcode'] = $grant_langcode;
    +          // The record with the original langcode is used as the fallback.
    +          if ($grant['langcode'] == $node->langcode) {
    +            $grant['fallback'] = 1;
    +          }
    +          else {
    +            $grant['fallback'] = 0;
    +          }
    +          $query->values($grant);
    +        }
    

    If some hook implementation defines a langcode, but language is off or the language is not enabled, it looks like this would blow up. Did I say that already and ask for a test for it? Edit: No, I think I said maybe it was on the contrib module to add language to its requirements. However, let's make sure we document that somehow, somewhere. If you execute a db_insert() with no values, does it simply insert no records, or does it fail? Can someone test this?

Then, some things I noticed reading through the patch (hopefully) one last time are documentation nitpicks that can be addressed in a novice followup issue. (These were the things I referred to as "comments need clarification" before I dropped off the map again up in #136. Please file that issue as well and link it here.

  • +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
    @@ -0,0 +1,271 @@
    +    // No language private Node:
    ...
    +    // No language public Node:
    

    "Node" does not need to be capitalized.

  • +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.phpundefined
    @@ -0,0 +1,271 @@
    +    // The only existing language (not specified) is set as private, so no
    +    // access on every language
    

    Missing a period.

  • +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
    @@ -24,34 +24,20 @@ class NodeAccessLanguageTest extends NodeTestBase {
    +    // Enable the private node feature of node_access_test module.
    

    "of the node_access_test module"

  • +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
    @@ -61,31 +47,196 @@ function testNodeAccess() {
    +    // Creating a public node with langcode Hungarian, will be saved as
    +    // the fallback in node access table.
    ...
    +    // Creating a private node with langcode Hungarian, will be saved as
    +    // the fallback in node access table.
    ...
    +    // Creating a private node with no special langcode, like when no language
    +    // module enabled.
    ...
    +    // Creating a private node with langcode Hungarian, will be saved as
    +    // the fallback in node access table.
    ...
    +    // Creating a public node with langcode Hungarian, will be saved as
    +    // the fallback in node access table.
    ...
    +    // Creating a public node with no special langcode, like when no language
    +    // module enabled.
    

    These three comments are a little unclear (same for the similar comments in the other tests).

  • +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.phpundefined
    @@ -61,31 +47,196 @@ function testNodeAccess() {
    +    // Tests that access is not granted if requested with no language.
    ...
    +    // Tests that access is not granted if requested with Hungarian language.
    ...
    +    // Tests that access is not granted if requested with no language.
    ...
    +    // Tests that Hungarian is still not accessible.
    ...
    +    // Tests that Catalan is still not accessible.
    

    These can all be "Test" or "Verify" or something rather than "Tests". (Ditto for similar comments elsewhere.)

  • +++ b/core/modules/node/node.installundefined
    @@ -710,6 +724,34 @@ function node_update_8014() {
    + * Add language.langcode and fallback field to {node_access} table.
    

    "Add the langcode and fallback fields to the {node_access} table," or maybe "Add language support to the {node_access} table"?

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2597,7 +2597,24 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
    +    // Initialize the langcode as an empty string.
    

    For this comment, I'm more interested in why we initialize it as an empty string, and what happens if it stays that way. (I don't need the comment to tell me what is clear on the next line.) :)

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2642,10 +2659,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
    +      // Only interested for granting in the current operation.
    

    I don't quite understand this comment.

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2642,10 +2659,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
    +      // If node is published also take the default grant into account, the
    +      // default is saved with nid = 0.
    

    "If the node is published, also take the default grant into account. The default is saved with a node ID of 0."

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2947,8 +2975,8 @@ function node_query_node_access_alter(AlterableInterface $query) {
    +      // If any grant exists for the specified user,
    +      // then user has access to the node for the specified operation.
    

    This comment is not wrapped correctly.

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2963,10 +2991,26 @@ function node_query_node_access_alter(AlterableInterface $query) {
    +      // Add langcode based filtering if on a multilingual site.
    

    langcode-based. Also I'd replace "if on" with "if this is a".

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2963,10 +2991,26 @@ function node_query_node_access_alter(AlterableInterface $query) {
    +        // If no specific langcode to check for is given, use the grant entry
    +        // which is set as fallback.
    

    "as the fallback"

  • +++ b/core/modules/node/node.moduleundefined
    @@ -2963,10 +2991,26 @@ function node_query_node_access_alter(AlterableInterface $query) {
    +        // If specific langcode is given, use the grant entry for it.
    

    "If a specific langcode is given"

  • +++ b/core/modules/node/node.moduleundefined
    @@ -3036,18 +3077,35 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
    +    // If we have defined a granted langcode, use it. But if not, add a grant for
    +    // every language this node is translated to.
    

    I think this comment is not wrapped properly.

  • +++ b/core/modules/node/node.moduleundefined
    @@ -3629,7 +3687,9 @@ function node_modules_disabled($modules) {
    +    // to remove any language specific grants.
    

    language-specific

Gábor Hojtsy’s picture

Assigned: Unassigned » Dries

It is pointless to pour more time into this, if Dries does not commit to possibly having this functionality in core. I will not ask anybody to spend more time on this unless we have confirmation it makes sense. I can easily encourage people to work on other useful/important things. Assigning to Dries for review.

xjm’s picture

Thanks @Gábor Hojtsy, makes sense.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

I was mainly worried about scaling but this only creates additional records for those nodes which have translations. Back to RTBC

xjm’s picture

Yay! Do we want to move #187 to a followup then? Edit: One part of it (about the hook docs) is sort of docs gate, though, unless I'm mistaken.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests, +Needs documentation, +Entity system, +Node access, +D8MI, +sprint, +language-content, +translation editorial workflow, +epic

The last submitted patch, language-grants-1658846-181.patch, failed testing.

Gábor Hojtsy’s picture

Sent for a re-test. Also I agree at least the invocation of hook_node_access_tag_subquery_alter() should be removed, since there are no implementations. It was for the prior state when language module implemented some query altering, but after that we agreed that was a wrong approach.

penyaskito’s picture

Assigned: Dries » penyaskito
Status: Needs work » Needs review
FileSize
60.48 KB

Rerolled, still needing work for the comments (set to NR for knowing if it applies OK)

Status: Needs review » Needs work

The last submitted patch, language-grants-1658846-195.patch, failed testing.

xjm’s picture

We broke this more today with #1862750: Implement entity access API for nodes. :(

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
60.57 KB

Straight reroll for #197.

effulgentsia’s picture

+++ b/core/modules/node/node.module
@@ -2522,6 +2522,18 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
+    // If the Language module is enabled, try to use the language from
+    // content negotiation.
+    if (module_exists('language')) {
+      // Load languages the node exists in.
+      $node_translations = $node->getTranslationLanguages();
+      // Load the language from content negotiation.
+      $content_negotiation_langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
+      // If there is a translation available, use it.
+      if (isset($node_translations[$content_negotiation_langcode])) {
+        $langcode = $content_negotiation_langcode;
+      }
+    }

This logic probably should be moved into NodeAccessController, but didn't do so in #198 in order to keep that a straight reroll. I think that can be follow up material though, unless someone is inspired to do it here.

Status: Needs review » Needs work

The last submitted patch, language-grants-1658846-198.patch, failed testing.

Gábor Hojtsy’s picture

Fails with fatal errors like this among others:

Fatal error: Call to undefined function Drupal\node\Tests\language_save() in /var/lib/drupaltestbot/sites/default/files/checkout/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php on line 56
FATAL Drupal\node\Tests\NodeAccessLanguageAwareCombinationTest: test runner returned a non-zero error code (255).

Fatal error: Call to undefined function Drupal\node\Tests\language_save() in /var/lib/drupaltestbot/sites/default/files/checkout/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php on line 62
FATAL Drupal\node\Tests\NodeAccessLanguageAwareTest: test runner returned a non-zero error code (255).

Fatal error: Unsupported operand types in /var/lib/drupaltestbot/sites/default/files/checkout/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php on line 268
FATAL Drupal\node\Tests\NodeAccessLanguageTest: test runner returned a non-zero error code (255).

Gábor Hojtsy’s picture

Also note that if this is not solved, we'll need to throw away node access records in the process with #1952044: Migration path for legacy content translation module data (ie. we will not be able to provide an upgrade path for node access).

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
12.33 KB
60.52 KB

The failures in #198 are the same ones that also existed prior to #197. Here's a fix for all but the private file test. Will look at that one next.

Note that I only joined this issue after #197, so I don't know where this issue is in terms of #191, #194, or other feedback.

Status: Needs review » Needs work

The last submitted patch, language-grants-1658846-203.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
728 bytes
61.35 KB

I copied a hack that already exists in HEAD's ImageItem::setValue() to FileItem::setValue(). I didn't track down exactly why the private file test doesn't fail the same way in HEAD, but only surfaces with the code in this issue, but frankly, I don't think it matters. It's something we'll need to clean up in both ImageItem and FileItem, and not really this issue's concern.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

As per @penyaskito this still needs to resolve comments from @xjm, at least this one:

+++ b/core/modules/node/node.moduleundefined
@@ -2818,10 +2833,26 @@ function node_query_node_access_alter(AlterableInterface $query) {
 
+      drupal_alter('node_access_tag_subquery', $subquery, $query);
+

This should be removed. Not used in the patch anymore. @xjm requested it be documented, but we have no use for it.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
62.03 KB
7.16 KB

Thanks Alex for taking care on this.

I removed the unnecessary new drupal_alter, and addressed some of the IMHO more important comments from @xjm (#1658846-187: Add language support to node access grants and records). We can afford them in followups if others agree.

#1658846-199: Add language support to node access grants and records still pending too, we can create a followup too.

diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index d6c7e13..ef55586 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2508,10 +2508,6 @@ 
  * @see node_menu()
- *
- * @todo
- *   Add langcode support to node_access schema / queries.
- *   http://drupal.org/node/1658846
  */

Removed the @todo, because this epic is hopefully addressed now, yay!

YesCT’s picture

follow-up for #199 #1953892: Move language node access logic into NodeAccessController

I'll work on figuring out what others are needed and opening them next.

YesCT’s picture

Noting what was done about each point in #187

1.
#1953898: Refactor Core Node Access Tests to not only test for denies, also test for public grants

2.
drupal_alter already removed. See: #206 and #207

3.
I dont understand what documentation should be added. Later for each check it explains that it's using user 1 since it has permissions to get everything.
Is there really something todo here? I dont think so.

4.
#1953900: Add a test for a hook implementation that defines a langcode but language module is not enabled.

Other things from xjm addressed in #207 (or coming right away in a patch) except:
unclear comments
#1953904: Clarify comments mentioning fallback and default saving behavior for langcodes

clean up tense verb for statements inside the tests to be "Test that" instead of "Tests that". This is probably in many places in core. Lets see how wide to make the cleanup in the follow-up
#1953916: Make tense of verb in inline comment be Test .. instead of Tests ... (node access, maybe more places in core)

Next steps:
patch coming for some things I saw need to be fixed.
Also, Need to:
update issue summary with a follow-ups section.
update motivation to include this is needed for removing legacy translation module.
add a related issues section. Specify which issues are blocked and waiting on this.

YesCT’s picture

Attached patch addresses these things, and a bunch of wrap to 80 chars for comments.
This could still use another read through of the whole thing.

+++ b/core/modules/node/lib/Drupal/node/NodeAccessController.phpundefined
@@ -109,11 +109,19 @@ protected function accessGrants(EntityInterface $node, $operation, $langcode = L
+    // If node is published also take the default grant into account, the
+    // default is saved with nid = 0.

grammar fix needed still.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+ * @file
+ * Contains Drupal\node\Tests\NodeAccessLanguageAwareCombinationTest.

since we started on this, 1354 has been updated to clarify that namespaces in comments should start with \.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+  function setUp() {
+    parent::setUp();

http://drupal.org/node/325974

public function setUp()

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+    // Add Hungarian and Catalan.
+    $language = new Language(array(
+      'langcode' => 'hu',

adding a language causes the ui .po file to be imported automatically. In other issues, we are using xx or some langcode that does not have a translation file, in order to not slow down testing. Should we do that here? Maybe this is bypassing the importation, since it's creating the language object and not going through the add language ui.

This could be a followup to find places where using xx in core instead of adding a language could speed up testing (and speed up the testbot).

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+    // Load the user 1 user for later use.

changing this to clarify that it's just convienient to use as an admin with all permissions who should be able to see everything.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+    // Query the nodes table as admin user (full access) with the node
+    // access tag and langcode de.

this and other comment multiline comments should wrap at 80 chars.

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.phpundefined
@@ -0,0 +1,324 @@
+    // Even though there is no German translation, all nodes are returned
+    // because node access filtering does not occr when the user is user 1.

occr should be occur

does this mean other things from catch's review in #149 are not done? I checked and other things look to be addressed, or were explained in comment on the issue.

+++ b/core/modules/node/node.installundefined
@@ -710,6 +724,34 @@ function node_update_8014() {
+ * Add language support to the {node_access} table

this was a correction from #187 of:

+++ b/core/modules/node/node.installundefined
@@ -710,6 +724,34 @@ function node_update_8014() {
+ * Add language.langcode and fallback field to {node_access} table.

"Add the langcode and fallback fields to the {node_access} table," or maybe "Add language support to the {node_access} table"?

But now it is missing the period.

YesCT’s picture

Issue summary: View changes

Significantly updated summary.

YesCT’s picture

the alter mentioned needing documentation in #168 and #187 was removed (#206). So removing need documentation tag.

Also, this has tests, and follow-ups are open for any additional ones needed. so removing needs tests tag too.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Avoid commit conflicts

The changes in docs comments from @YesCT and @penyaskito look good! I'm sure we can find as much to improve as we like, and all recent comments evolved around comments needing more cleanup. YesCT opened several followups to track these further. Given the extent of reviews this has gotten as well as the prestigious and very hard working set of people on the issue, not to mention the repeated changes around this area of code in core, I think its about time to get the foot in core.

Just looking at what would dreditor generate for the issue, the list of key contributors in summary: penyaskito, Schnitzel, xjm, agentrickard, effulgentsia, kalman.hosszu, vijaycs85, Gábor Hojtsy, disasm, YesCT Impressive!

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for re-rolling this patch. Committed it to 8.x. Thanks!

Gábor Hojtsy’s picture

Title: Add language support to node access grants and records » Change notice for: Add language support to node access grants and records
Priority: Major » Critical
Status: Fixed » Active
Issue tags: -Avoid commit conflicts +Needs change record

Amazing, thanks, now for the change notice.

plach’s picture

Whoo-hoo!
(hell of reroll ahead ;)

plach’s picture

Er, too much enthusiasm, I guess

Schnitzel’s picture

wow, nice! thanks for all which worked on this :)

Gábor Hojtsy’s picture

Title: Change notice for: Add language support to node access grants and records » Add language support to node access grants and records
Priority: Critical » Major
Status: Active » Fixed
Issue tags: -Needs change record, -sprint

Added an extensive change record at http://drupal.org/node/1959668 with code examples, figures, etc. Please reopen if needs improvement. Also cross-linked with the original node access runtime language support change notice from http://drupal.org/node/1667616.

plach’s picture

Awesome work, thanks!

agentrickard’s picture

@Gábor Hojtsy

A code sample explaining this line would be useful as well: "Node access grant providers should use the same logic to return multilingual grants only on multilingual sites."

Gábor Hojtsy’s picture

@agentrickard: added this whole new section, hopefully this helps :) Please verify.

Node access hook writing tips

1. If your node access module does not want to deal with language variance, you can just ignore this new feature altogether. If you don't include a langcode as part of your grant, the grant will be saved for all langcodes that the node is available in / translated to. If on a single language site, the node can only have a single language, so it will just work. If on a multilingual site, the grant will be saved for all language versions of all nodes as appropriate.

// No langcode returned in grants makes them save for all langcodes appropriate for the node.
function example_node_access_records(Drupal\node\Node $node) {
  // ...
  $grants[] = array(
    'realm' => 'example',
    'gid' => 1,
    'grant_view' => 1,
    'grant_update' => 0,
    'grant_delete' => 0,
  );
  // ..
  return $grants;
}

2a. If you do want to have variance per language for the node access grants returned, that is use this new feature at all, you should make sure not to return multilingual grants if the site is not multilingual. Just return the grant for the node's language or return grants without language.

// Make sure to only generate grants for langcodes appropriate for the node.
// On single language sites, the node will never have multiple languages (the
// array returned by getTranslationLanguages() will always have one element 
// only), so the node access records will stay unique.
function example_node_access_records(Drupal\node\Node $node) {
  // ...
  // ... Retrieve $langcode_dependent_view_value some way from config ...
  // ...
  foreach ($node->getTranslationLanguages(TRUE) as $langcode => $language) {
    $grants[] = array(
      'realm' => 'example',
      'gid' => 1,
      'grant_view' => $langcode_dependent_view_value[$langcode],
      'grant_update' => 0,
      'grant_delete' => 0,
      'langcode' => $langcode,
    );
  }
  // ..
  return $grants;
}

2b. Alternatively you can use different logic for single language and multilingual scenarios:

function example_node_access_records(Drupal\node\Node $node) {
  // ...
  if (!language_multilingual()) {
    // Only assemble $grants arrays here *without* a langcode (or only use $node->langcode).
  }
  else {
    // Assemble $grants arrays here with langcode as appropriate for your logic.
  }
  return $grants;
}

Note that you never need to specify the 'fallback' key in the grants, that is always automatically added to the grant (or overwritten with the right value if provided improperly).

agentrickard’s picture

What I meant was that I need an example of hook_node_grants(). Currently, the grants array returned makes no account of language. Is that handled automatically by the system?

function hook_node_grants($account, $op) {
  if (user_access('access private content', $account)) {
    $grants['example'] = array(1);
  }
  $grants['example_owner'] = array($account->uid);
  return $grants;
}

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

Anonymous’s picture

Issue summary: View changes

added issues this is blocking, updated motivation to clarify cannot remove legacy module, added follow-up section, added related issues.

penyaskito’s picture

Assigned: penyaskito » Unassigned