Why does restrictedTraverse() in Zope 2 not respect IPublishTraverse adapters?
Hi, There's currently a funny inconsistency in Zope's Traversable class. If you have a URL like http://localhost:8080/path/to/@@aview/foo, and @@aview implements IPublishTraverse (and, I presume, if there's a custom IPublishTraverse adapter for any other path component), URL traversal will work fine, but calling to.restrictedTraverse('@@aview/foo') or some variant thereof will fail, because (un)restrictedTraverse() does not respect custom IPublishTraverse adapters. I can kind of see why it's done like this since it's called I*Publish*Traverse, but it is a pain. Note that namespace traversal (like ++skin++) works fine with restrictedTraverse(). I don't think it'd be hard to implement this, but: - is this a bug? - is there a reason not to do this? Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Martin Aspeli wrote:
There's currently a funny inconsistency in Zope's Traversable class. If you have a URL like http://localhost:8080/path/to/@@aview/foo, and @@aview implements IPublishTraverse (and, I presume, if there's a custom IPublishTraverse adapter for any other path component), URL traversal will work fine, but calling to.restrictedTraverse('@@aview/foo') or some variant thereof will fail, because (un)restrictedTraverse() does not respect custom IPublishTraverse adapters.
'restrictedTraverse' is not (and never has been) the same as URL traversal. For instance: - - URL traversal does no security checking until it finds the published object. - - URL traveresal manages the '__before_publishing_traverse__' hooks. If you want your adapter to be respected by *both*, it needs to implement the appropriate interfaces for both.
I can kind of see why it's done like this since it's called I*Publish*Traverse, but it is a pain.
Note that namespace traversal (like ++skin++) works fine with restrictedTraverse().
I don't think it'd be hard to implement this, but:
- is this a bug?
No.
- is there a reason not to do this?
- -1 to adding any more majyk to the over-complicated Z3-style traversal dance inside Zope2, especially as it would involve a bunch of subtle behavior changes which would be hard to explain. For maximum portability across Z2 / Z3 / BFG, you could just do the same thing and implement __getitem__ on any object you want to be traversable by either the publisher or APIs like (un)restrictedTraverse, and forego the over-complicated component-laden traversal dance. ;) Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFKDEz/+gerLs4ltQ4RAlYAAJ436Gtk2+ibpVAX/8H+Q6BOJ3+AWQCfT7HC kDTUth7NAOCOt6yrAWR20jY= =NvUz -----END PGP SIGNATURE-----
Tres Seaver wrote:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Martin Aspeli wrote:
There's currently a funny inconsistency in Zope's Traversable class. If you have a URL like http://localhost:8080/path/to/@@aview/foo, and @@aview implements IPublishTraverse (and, I presume, if there's a custom IPublishTraverse adapter for any other path component), URL traversal will work fine, but calling to.restrictedTraverse('@@aview/foo') or some variant thereof will fail, because (un)restrictedTraverse() does not respect custom IPublishTraverse adapters.
'restrictedTraverse' is not (and never has been) the same as URL traversal. For instance:
- - URL traversal does no security checking until it finds the published object.
- - URL traveresal manages the '__before_publishing_traverse__' hooks.
If you want your adapter to be respected by *both*, it needs to implement the appropriate interfaces for both.
I can kind of see why it's done like this since it's called I*Publish*Traverse, but it is a pain.
Note that namespace traversal (like ++skin++) works fine with restrictedTraverse().
I don't think it'd be hard to implement this, but:
- is this a bug?
No.
- is there a reason not to do this?
- -1 to adding any more majyk to the over-complicated Z3-style traversal dance inside Zope2, especially as it would involve a bunch of subtle behavior changes which would be hard to explain.
For maximum portability across Z2 / Z3 / BFG, you could just do the same thing and implement __getitem__ on any object you want to be traversable by either the publisher or APIs like (un)restrictedTraverse, and forego the over-complicated component-laden traversal dance. ;)
Minimal example demonstrating this with a view in zope2:
from zope.component import getSiteManager from Testing.makerequest import makerequest from zope.publisher.browser import IBrowserView from Acquisition import Explicit from zope.component import getSiteManager app = makerequest(app) smgr = getSiteManager() class Foo(Explicit): ... def __init__(self, context, request): ... self.context, self.request = context, request ... def __getitem__(self, key): ... return int(key) ... smgr.registerAdapter(Foo, (None, IRequest), IBrowserView, name='foo') app.unrestrictedTraverse('@@foo/12345') 12345
Laurence
On Thu, May 14, 2009 at 10:55:40PM +0200, Laurence Rowe wrote:
For maximum portability across Z2 / Z3 / BFG, you could just do the same thing and implement __getitem__ on any object you want to be traversable by either the publisher or APIs like (un)restrictedTraverse, and forego the over-complicated component-laden traversal dance. ;)
Minimal example demonstrating this with a view in zope2:
from zope.component import getSiteManager from Testing.makerequest import makerequest from zope.publisher.browser import IBrowserView from Acquisition import Explicit from zope.component import getSiteManager app = makerequest(app) smgr = getSiteManager() class Foo(Explicit): ... def __init__(self, context, request): ... self.context, self.request = context, request ... def __getitem__(self, key): ... return int(key) ... smgr.registerAdapter(Foo, (None, IRequest), IBrowserView, name='foo') app.unrestrictedTraverse('@@foo/12345') 12345
Thanks for reminding me of this. I keep forgetting that this works! I only add that if you want to use __getitem__ for publishing, the items you return should inherit from Acquisition.(Im|Ex)plicit to make the security machinery happy. -- Paul Winkler http://www.slinkp.com
Some days ago I ran into the same problem, and have been pointed to this thread. Maybe you are interested in my solution. It's ugly, but I needed it for a test-case, where I wanted to access "@@plone_context_state/is_view_template": >>> from ZPublisher.HTTPRequest import HTTPRequest >>> from ZPublisher.HTTPResponse import HTTPResponse >>> from Products.PloneTestCase import PloneTestCase >>> import base64 >>> user_id = PloneTestCase.default_user >>> password = PloneTestCase.default_password >>> encoded = base64.encodestring( '%s:%s' % ( user_id, password ) ) >>> auth_header = 'basic %s' % encoded >>> resp = HTTPResponse() >>> env={'SERVER_URL':'http://nohost/plone', ... 'URL':'http://nohost/plone', ... 'HTTP_AUTHORIZATION': auth_header, ... 'REQUEST_METHOD': 'GET', ... 'steps': [], ... '_hacked_path': 0, ... '_test_counter': 0, ... } >>> request = HTTPRequest(stdin=None, environ=env, response=resp) >>> request['PARENTS'] = [self.getPortal()] >>> contextState = request.traverse("weblog/issue193/" ... "@@plone_context_state") >>> request['ACTUAL_URL'] = 'http://nohost/weblog/issue193' >>> contextState.is_view_template() True >>> request.close() Grüße Jan Hackel Martin Aspeli wrote:
There's currently a funny inconsistency in Zope's Traversable class. If you have a URL like http://localhost:8080/path/to/@@aview/foo, and @@aview implements IPublishTraverse (and, I presume, if there's a custom IPublishTraverse adapter for any other path component), URL traversal will work fine, but calling to.restrictedTraverse('@@aview/foo') or some variant thereof will fail, because (un)restrictedTraverse() does not respect custom IPublishTraverse adapters.
Jan Hackel wrote:
Some days ago I ran into the same problem, and have been pointed to this thread. Maybe you are interested in my solution. It's ugly, but I needed it for a test-case, where I wanted to access "@@plone_context_state/is_view_template":
>>> from ZPublisher.HTTPRequest import HTTPRequest >>> from ZPublisher.HTTPResponse import HTTPResponse >>> from Products.PloneTestCase import PloneTestCase >>> import base64 >>> user_id = PloneTestCase.default_user >>> password = PloneTestCase.default_password >>> encoded = base64.encodestring( '%s:%s' % ( user_id, password ) ) >>> auth_header = 'basic %s' % encoded >>> resp = HTTPResponse() >>> env={'SERVER_URL':'http://nohost/plone', ... 'URL':'http://nohost/plone', ... 'HTTP_AUTHORIZATION': auth_header, ... 'REQUEST_METHOD': 'GET', ... 'steps': [], ... '_hacked_path': 0, ... '_test_counter': 0, ... } >>> request = HTTPRequest(stdin=None, environ=env, response=resp) >>> request['PARENTS'] = [self.getPortal()] >>> contextState = request.traverse("weblog/issue193/" ... "@@plone_context_state") >>> request['ACTUAL_URL'] = 'http://nohost/weblog/issue193' >>> contextState.is_view_template() True >>> request.close()
For this, you should possibly use zope.testbrowser (and Products.Five.testbrowser). The reason you needed this particular hack was that the is_view_template() method looks at the url quite specifically. For most cases, context.restrictedTraverse('@@plone_context_state') would've worked. Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book
participants (5)
-
Jan Hackel -
Laurence Rowe -
Martin Aspeli -
Paul Winkler -
Tres Seaver