If a query has the tag commerce_product_access, then commerce_product_query_commerce_product_access_alter() is called. This function calls commerce_entity_access_query_alter() with $entity_type = "commerce_product". Under some conditions, commerce_entity_access_query_alter() will add a bundle condition to the query. If the base table of the query matches the entity type, this condition works as intended. However, if the base table is different (e.g commerce_order), the new condition is applied to the base table and not to the commerce_product table. This almost always produces incorrect results because product bundles almost never coincide with order bundles.

The attached text files describe the view that surfaced this bug (base table commerce_order, joined to commerce_product) and the query before and after the call to commerce_product_query_commerce_product_access_alter(). You can see that the altered query has a condition on commerce_order.type; this condition is added at line 1173 of commerce.module:

http://drupalcode.org/project/commerce.git/blob/refs/heads/7.x-1.x:/comm...

One fix could be to use the appropriate table name on that line. But I don't know how to figure out that table name (it might be aliased).

This bug might be a duplicate of #1276450: Views results empty for unprivileged user when using Relationship: Content: Referenced Product.

Comments

anybody’s picture

rszrama’s picture

This is actually unrelated. That other issue has to do with a legitimate access control check afaik; disabling SQL rewriting on the View will solve that.

This issue seems to be bumping up against what we've documented would happen here:

http://drupalcode.org/project/commerce.git/blob/refs/heads/7.x-1.x:/comm...

Note that it basically says if the query alter function wasn't passed a base table, all we can do is guess, and we'll likely be wrong. I wonder if commerce_product_query_commerce_product_access_alter() doesn't need to pass 'commerce_product' or some alias to commerce_entity_access_query_alter() as a base table parameter.

ezheidtmann’s picture

I think it's guessing the base table correctly, but then it's treating it as if it matches the entity type. Line 1183:

http://drupalcode.org/project/commerce.git/blob/refs/heads/7.x-1.x:/comm...

Thus when the base table is commerce_order but the entity type is commerce_product, the query is wacky because the condition is looking for an order bundle with the same name as the product bundle.

Maybe commerce_entity_access_query_alter() should ensure that the entity type table is either the base table or is present on the query already, add a join if necessary, and then add the conditions.

P.S. Disabling SQL rewriting on the View also resolves this issue, for some limited value of "resolves".

rszrama’s picture

Hah, for what it's worth, I maintain it's safe to disable SQL rewriting on Views where you know you're doing other filtering. For example, if the order would only ever join to line items that you know are on it and you've confirmed that the user has access to view the order through other filters / contextual filters, there's no harm in disabling rewriting so they have access to view any of the referenced line items or their referenced data.

Ultimately I think that's a better solution than essentially duplicating access control by leaving the check in place even though we know through other means that the user should have access to view the data.

rszrama’s picture

Status: Active » Needs review
StatusFileSize
new4.43 KB

Alrighty, I did a fair bit of digging into this this afternoon and evening. I think I turned up an unrelated bug and conceived of a more intelligent way to establish a base table, because it's obviously incorrect for a commerce_product entity access check to be using commerce_order as the base table for its rewriting. : )

Fixing the base table

If a $base_table parameter currently isn't passed in to the function, it just takes the first one from the tables array and uses it, even though it's likely incorrect. We've even documented it's incorrect, but we never made an attempt to get a nearer match.

However, the tables array actually contains the information we need to make a better guess. I've updated the comments and code to indicate that I'm looping through the tables to see if any of them match the base table of the access query's entity type, and if so I'm using that table's alias as the new $base_table. In a quick test, this had the desired effect for this particular scenario.

Fixing an unknown problem with $really_restricted

I can't remember all the details surrounding our inclusion of the $really_restricted parameter. I'm pretty sure it was something of a premature optimization; we determined that we didn't need to add any additional conditions if the user had "view any * entity of bundle *" because that would trump the more specific ownership checks. Thus if we got to the end of the bundle checks and the user only had "view any * entity of bundle *" permissions, we exited out of the function.

This means that the $conditions array was never actually added to the $query, though, because we don't get around to doing that until the end of the function. : P

I've addressed this in the same patch by simply removing $really_restricted altogether and adding some additional documentation to the creation of the $conditions OR group. Since this is an OR group, we don't need to exit out early. There's no problem in us having multiple conditions on the query with some failing if the original one passes, and it is feasible that someone might have a "view any * entity of bundle *" permission that doesn't affect a query but a "view own * entities" permission that does. That scenario would currently be unsupported by Commerce.

I'm going to get a review from Damien on this before moving forward (he may remember the context of $really_restricted), but the patch is posted for you and the test bot to test. : )

rszrama’s picture

Status: Needs review » Needs work
rszrama’s picture

Status: Needs work » Needs review

Reactivating the test bot.

rszrama’s picture

Let's try just reposting the patch.

rszrama’s picture

Status: Needs review » Fixed

Damien clarified $really_restricted for me. While I read it above as potentially a premature optimization, I was wrong. It's a safe assumption and a useful optimization. I was missing the fact that if more than one product types were present on the site and the user only had access to view all products of one or the other type, it would fall down through this if / else statement to set $really_restricted to TRUE. But in the event there's only one product type and we detected the user has access to view any products of that type, it's completely safe to exit early - neither the product type check nor the falsifying condition will be added and the view query would pass as expected.

Regarding the first hunk, we took a look at _node_query_node_access_alter() to ensure our function matched the core behavior for determining a base table. It appears this patch gets most of the way there, though I apparently need to take into consideration that a table in the query's tables array may itself be a select query (presumably accommodating joins to the results of subqueries). Node does offer some manner of support for foreign keys, but I don't think that's essential for us to implement here. Additionally, I'm not sure node doesn't have a bug in using the table name instead of its alias name, but perhaps there's some other mechanism in the Node module for joining to the table directly that I'm unaware of. : ?

Commit: http://drupalcode.org/project/commerce.git/commitdiff/5211db5

Status: Fixed » Closed (fixed)

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