Updated: Comment #334
Problem/Motivation
Currently, when a node table is left joined into a query, adding a node_access tag to the query filters out accessible rows from the base table (effectively acting more like an inner join). In particular, rows containing null values are incorrectly filtered out by node_access (i.e., if the base table has null entries for the node ID, node_access denies access to all users based on those null values, even though they should have no relevance to node_access checks). The most common example is a view where rows disappear from the table after adding an optional relationship to the view.
See the tests in patch #326 to reproduce, or more manually in D7:
1. Install Entityreference and Views,
2. Create a field referencing nodes (using entityreference),
3. Create a node having the entityreference field empty,
4. Create a View with a relationship using the entityreference field. Do NOT select "Require this relationship".
5. Expected result is that regular users can see the node in the View. Current result is that regular users cannot see the node.
Proposed resolution
The problems can be fixed by altering how the node_access conditions are added into the query whenever the node table is a joined table. Currently the conditions are added to the overall query conditions; the proposal is to instead add the node_access conditions into the join conditions.
This approach maintains the integrity of the node_access checks: all nodes to which the current user is denied access are removed from the query results. However, it makes as few additional changes as possible to the query results.
The primary effect is that rows containing optional, empty (null node ID) entries are no longer removed. Furthermore, if rows contain optional (left-joined) content from an access-denied node, only the content of the denied node is removed from the results. In the context of a view, this means that any individual cells of a table containing restricted content will be blanked, but non-restricted content in the rest of the row is still visible to the user.
Adding the node_access conditions into the join conditions is made more difficult by limitations of the database API: #2275519: Unable to use Condition objects with joins. The current patch has opted to add this missing feature to the database API, instead of implementing more complex code that tries to work around the limitation.
Both D8 (#326) and D7 (#332) patches implementing this approach have been provided.
Remaining tasks
- Patch needs to be reviewed by the community. Earlier patches (in particular D7) were extensively reviewed, but more feedback on the current patch is needed. Some specific recent questions include:
- Is the current patch, which avoids an API change, preferable (see #321)?
- Is it appropriate for a bug-fix patch to incorporate code for a requested feature (see #319)?
- Are there use cases that cannot be handled by patch #326, and would instead require patch #302? (See also Detailed Example)
Write tests.(Patch #326 contains comprehensive tests, including nested joins and all combinations of accessible and restricted content.)In Postgres with 3 node tables and a count query, placeholders are getting inserted in the wrong sequence.(This was an issue for patch #149 but was reported as fixed by a subsequent patch. The current D8 patch doesn't edit the placeholders, and has been tested successfully using PostgreSQL.)Address issue of node access with a type of "entity" -- no known cases where this bug is triggered, recommend creating new issue.(The entity-specific query alterations have been removed in D8, making the issue no longer relevant.
User interface changes
None
API changes
None
Data model changes
None
Detailed example
As an example, a site has 'page' nodes containing a field that is a reference to a related 'article' node. A view is created to list the page nodes: the page title is shown in the first column; the related article's title is shown in the second column (using an optional relationship). The following is the view as seen by an admin. Any node with 'public' in the title is visible to everyone; any node with 'private' in the title should not be visible to regular users.
Case A: admin view |
---|
Page title | Article title |
---|
Public-page-1 | Public-article-a |
Public-page-2 | [none] |
Public-page-3 | Private-article-b |
Private-page-4 | Private-article-c |
|
With current code, the table shown to non-admins incorrectly removes some rows containing public pages:
Case B: public view, unpatched |
---|
Page title | Article title |
---|
Public-page-1 | Public-article-a |
With patch #326, regular users will see:
Case C: public view, patched |
---|
Page title | Article title |
---|
Public-page-1 | Public-article-a |
Public-page-2 | [none] |
Public-page-3 | [none] |
All private pages and private articles have been filtered out. Public-page-3 is still displayed because it is public content; the reference to private-article-b is in an optional column, implying that unavailable content should result in a blank cell instead of removing the entire row.
Some users may prefer to construct a view where the entire row is removed if it contains a reference to an inaccessible article. In many cases, such views will simply want to change the article column into a required column. If, however, it is necessary to keep rows with missing articles, and only filter rows with inaccessible articles, users can add the appropriate filter into the view, for example, "where article_title is null AND article_reference is not null". This would produce:
Case D: public view, alternate |
---|
Page title | Article title |
---|
Public-page-1 | Public-article-a |
Public-page-2 | [none] |
Note that one proposed patch, patch #302, uses a different approach that directly produces Case D, without the need for additional filters. However, using patch #302 it is impossible to produce Case C (except by gutting the node_access checks). In other words, patch #326 can be used to handle all reported use cases, but patch #302 cannot.
Original report by [username]
I'm loving the Entity Reference module, but I've come across some weird behavior. It's probably just something I've missed setting.
I have a content type called Album. I've got another content type called Review which has an entity reference to Album.
I've created a View which lists Albums. I wanted to include some fields from my Review content type, but I still want to list each Album, even if a review referencing the album has not been created yet.
In my view, under Relationships, I add an "Entity Reference: Referencing entity" relationship. I make sure "Require this relationship" is NOT checked.
When I'm logged in as an administrator, all Albums are returned, as expected. But when I'm not logged in, or I'm logged in as a regular user, only albums that are referenced by a review are displayed. I'm really puzzled as to why I get different results depending on my user role.
I get the same results regardless of if I add fields from the Review content type or not. I've tried clearing the cache multiple times, and I've tried checking and unchecking the "Require this relationship" box. I've deleted the relationship and tried adding it again, but I always get the same results.