[SOLVED] Caching on restricted page show cached reponse

I was done with a project, and send it to a client for testing.

The application has a restricted area, whichs showed a list of domain attached to the user.

Once the first user has logged in and confirmed that he could se hes own domains, the other user logged in but got the same list as the first user. This seems as a issue with either Fluid caching, query caching (none activated from my site) or perhaps the server that caches the response.

Should I be aware of anything special, when it comes to restricted pages, displaaying content by a query modified by my Policy.yaml privilege restrictions?

First of all it would be crucial to know which cache is responsible for this. To find that, try deactivating the caches in question one by one (e.g. set a NullBackend in Caches.yaml). The Fluid cache is unlikely, since it only caches the template but not the content. My best guess would be the query cache, so maybe start with that straight away.

Then when the baddy is found, we’d need to check why it is cached without taking login into consideration.

Thanks for the response :slight_smile: I disavled all doctrine related caches (taken from Caches.yaml from the Neos.Flow package but nothing changed.

Then i turned to my own class I’m using in my Policy.yaml stuff (yeah, that came to my mind all of sudden…)

Neos:
  Flow:
    aop:
      globalObjects:
        userService: Vendor\App\Domain\Service\UserService
```

Is made with inspiration from the Security Context class that is automatically added. Should I beaware of anything in regard of caching and proxy? It's created as `@Flow\Scope("session")` and used this way

````  'Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\EntityPrivilege':
    'Vendor.App:Entities.MyDomains':
      matcher: 'isType("Vendor\App\Domain\Model\Domain") && !(property("Persistence_Object_Identifier").in("context.userService.domainIdentifiers"))'
    'Vendor.App:Entities.MyPixels':
      matcher: 'isType("Vendor\App\Domain\Model\Item") && !(property("sourceHash").in("context.userService.itemHashes"))'
```
with a method looking like this

````	public function itemHashes() {
		if ($this->itemHashes === []) {
			$itemHashes = [];
			$user = $this->getCurrentUser();
			if (!($user instanceof User)) {
				return $itemHashes;
			}
			$items = $user->getItems();
			/** @var Item $item */
			foreach ($items as $item) {
				$itemHashes[] = $item->getItemHash();
			}
			$this->itemHashes = array_unique($itemHashes);
		}

		return $this->itemHashes;
	}
```

And with the property declared as follow
/**
 * @var array
 * @Flow\Transient
 */
protected $itemHashes = [];
and similar for the `getItemIdentifiers` method. The returned itemHashes is cached, not only for the current logged in user but for all users - so what seems to be doctrine issue, more looks like a cache/proxy issues of a class.

Anything particular I should be aware off? I depend on injection of classes, so trying to set  `Flow\Proxy("false")` makes the class none-functional due to classes not being injected.

All help appreciated :)

Narrowed it down a bit further, and found out that disabled Flow_Persistence_Doctrine with

Flow_Persistence_Doctrine:
    backend: Neos\Cache\Backend\NullBackend

stopped caching results. But how can this be nescessary in my case and not in the user service from Neos.Neos ? I think it’s at a deeper level, than I can currently grasp :slight_smile:

Ok, that kind of fits. The Flow_Persistence_Doctrine cache is the Doctrine Metadata and Query cache: The issue is most definitely the latter.
As Kay pointed out on slack, this could be related to this doctrine issue: https://github.com/doctrine/doctrine2/issues/3955

For the SecurityContext, the workaround in place in Flow is that the SecurityContext hash containing the currently authenticated roles is added to the SqlFilter parameters, which in turn ends up in the query cache identifier for Doctrine.
See Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlFilter line 68.

So the issue at hand is probably that your two distinct users have the same roles and hence end up using the same query cache, even though they have different domains. The assumption in place with Flow here is, that the roles are a sufficient distinction for caching - which it is for all generic authorization purposes.

A workaround could be to give a distinct role to each user (urgh) or either override the SecurityContext to add the user Account to the ContextHash or alternatively override the Flow SqlFilter to add another context parameter to the filter containing the user or some other identifying context.

That explains a lot - thanks for that pointer to the deeper level :slight_smile:

I see another way of solving it, could be by taking the current account into the calculated contextHash ? It’s a part of the security context, so I see it as valid for the calculation.

With the current implementation, I will need to override the SqlFilter, as I can’t affect the value the SqlFilter gets from the Security Context class (it’s not a interface I can override from Objects.yaml).

So perhaps we could do something more, while we are at it?

I see another way of solving it, could be by taking the current account into the calculated contextHash ?

Well, not generically, since it would highly degrade the caching effectiveness and in most cases it is good enough to only consider Roles. I could imagine some kind of feature flag/switch though, that could be enabled through settings to add the account to the context hash by the framework.

Maybe someone else has a better idea of how to make the user account considered for query caching through opt-in.

Can you explain this a bit more? If a application have 100 users with the same roles Vendor.App:RegisteredUser but what they can see is limited by a property, they will all generate the same contextHash and all see the same cached (from first hit) response.

If that is correct, I see don’t see roles as valid, when considering ex, EntityPrivilege where a property (not being roles) is what says what’s is right or wrong.

I stumbled upon the same issue some time ago and dug deep into Doctrine and if I remember correctly, it’s an issue in Doctrine’s caching of queries.

To solve it I used a workaround https://gist.github.com/aertmann/51ae0040b1ef179c208e#file-sqlfilter-php-L96-L97

1 Like

If a application have 100 users with the same roles Vendor.App:RegisteredUser but what they can see is limited by a property, they will all generate the same contextHash and all see the same cached (from first hit) response.

Yes (though doctrine query cache doesn’t cache results, but the generated SQL queries). And that is fully plausible under the general assumptions about how role based authorization works.

If that is correct, I see don’t see roles as valid, when considering ex, EntityPrivilege where a property (not being roles) is what says what’s is right or wrong.

Well, the difference is, that the property on the entity you are checking against is not a global variable which ends up in the query cache. Anything you check against in your models is no issue.

It’s a bit difficult, and I still don’t fully grasp when the issue really occurs, but for now just consider this: as long as your privileges (implicitly) only depend on the authenticated roles, everything works as expected. For the other case, where the privilege is different for each account, we should probably introduce some switch to support this mode.

1 Like

This seems to have been a valid issue and is fixed with the “Entity Security” release today

2 Likes