Ticket #9915 (reopened Bug)
borg.localrole caching objects using id(object)
Reported by: | evilbungle | Owned by: | |
---|---|---|---|
Priority: | major | Milestone: | 4.x |
Component: | Backend (Python) | Version: | 4.3 |
Keywords: | Cc: | optilude, alecm, hanosch |
Description
Summary: In borg.localrole.workspace.clra_cache_key, when generating the cache key for caching the result of checkLocalRolesAllowed, if the object does not have a getPhysicalPath attribute (such as a browser view), id(obj) is used instead (line 127). This assumes that the id() of that object is unique and constant for the lifetime of a request. This isn't necessarily the case, and it's possible to cause cache collisions using lots of restrictedTraverse calls. Collisions in this case result in the wrong set of local roles being returned.
History: We're seeing the problem because we have a browser view which is only visible in certain contexts - we know that somewhere in the acquisition chain the user will be able to see the view, so we run up through the aq_chain, try restrictedTraverse() and ignore any Unauthorized errors until we find a context where that view is usable. Since the security is tested after the view has been instantiated we're creating lots of instances of the view then immediately throwing them away. For some reason, there's a high chance that new instances of the same object share an id() which was previously used for a different instance (potentially in a different context!). When the local roles are read from the cache, the roles for a different context are returned, producing inconsistent results.
Potential fix: borg.localrole, workspace.py, line 127: change oid = id(obj) to raise DontCache. There's an obvious disadvantage to this, in that it means that local roles on browser views won't get cached.
Change History
comment:1 Changed 4 years ago by kleist
- Status changed from new to confirmed
- Cc changed from optilude,alecm to optilude, alecm
- Version set to 3.3
- Component changed from Unknown to Backend (Python)
- Milestone changed from 3.3.x to 4.x
comment:2 Changed 3 years ago by eleddy
- Status changed from confirmed to closed
- Resolution set to wontfix
This ticket has not been modified in over 9 months. In another brazen attempt to clean this tracker up, this is closed. If you really, REALLY care about this ticket, please re-verify that it is still an issue on the current supported releases (4.2 or 4.3) and reopen. Better yet, submit a pull request to fix the bug and then close the bug properly. We <3 you and all of your effort, but we can't go on like this anymore. I hope you aren't too mad and we can still be friends. Hugs.
comment:3 Changed 22 months ago by gaudenzius
- Cc hanosch added
- Status changed from closed to reopened
- Version changed from 3.3 to 4.3
- Resolution wontfix deleted
This issue is still present in Plone 4.3 and the current code in GIT. I just come from a 3+h debugging session to debug mysterious wrong results coming from the memoize cache because of this. Similar to the original reporter the code I was debuging created several instances of the same view for different objects with different local role assignments. If an view instance reuses the same memory location of a previously instantiated view which is already garbage collected, they get the same id and the wrong cached value is used.
The easiest solution to the problem is the following patch:
--- workspace.py.orig 2014-06-02 15:32:10.288052130 +0200 +++ workspace.py 2014-06-02 15:32:23.240426346 +0200 @@ -122,7 +122,7 @@ try: oid = obj.getPhysicalPath() except AttributeError: - oid = id(obj) + raise DontCache return (user.getId(), oid, tuple(object_roles)) def store_on_request(method, self, user, obj, object_roles):
This has the obvious disadvantage of not caching results for obj that don't have a physical path. Another solution might be to walk up the acquistion chain like this:
--- workspace.py.orig 2014-06-02 15:32:10.288052130 +0200 +++ workspace.py 2014-06-02 16:10:46.075276844 +0200 @@ -119,11 +119,18 @@ request = aq_get(obj, 'REQUEST', None) if IAnnotations(request, None) is None: raise DontCache + oid = create_oid(obj) + return (user.getId(), oid, tuple(object_roles)) + +def create_oid(obj): try: - oid = obj.getPhysicalPath() + return obj.getPhysicalPath() except AttributeError: - oid = id(obj) - return (user.getId(), oid, tuple(object_roles)) + oid = [id(obj),] + parent = aq_parent(aq_inner(obj)) + if parent is not None: + oid.extend(create_oid(parent)) + return oid def store_on_request(method, self, user, obj, object_roles): """ helper for caching local roles on the request """
If people agree that this is a good idea, I can put together a pull request. I'm not completely sure if the proposed solution is a good idea or if the recursion is actually more work than just not caching the result. After all this is only cached on the request, how many views are callled multiple times on the same request?
Gaudenz
comment:4 Changed 10 months ago by gaudenzius
- Status changed from reopened to closed
- Resolution set to fixed
I have now pushed the DontCache version of the change. I have been running this in production since about a year ago without any negative consequences. The fix will be in borg.localrole 3.0.3 and 3.1.2 which should be included in Plone 4.3.6 and the next Plone 5.0 beta.
comment:5 Changed 10 months ago by jensens
- Status changed from closed to reopened
- Resolution fixed deleted
Reverted, see message in https://github.com/plone/borg.localrole/commit/065e42ea529399ef2255f958694a9224dac92162
Please read first http://docs.plone.org/develop/plone-coredev/index.html
I also added an issue here https://github.com/plone/borg.localrole/issues/2 since dev.plone.org is going into archive mode (hopefully) soon.
https://github.com/plone/borg.localrole/blob/master/borg/localrole/workspace.py#L34
Still an issue in Plone 4?