Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work on fixing allowed_attributes and allowed_interface on pages #610

Closed
wants to merge 17 commits into from

Conversation

dataflake
Copy link
Member

@dataflake dataflake commented May 10, 2019

Fixes #397

This is work in progress so this is a "draft" PR that cannot be merged.

Open issues:

  • Move added implementation of publishTraverse to an adapter (as requested by @jaroel) does not help, the problem is that authentication is done after traversal
  • Convert newly written doctest to a unittest.
  • Add a similar test for the behaviour when accessing a not published attribute (aka mouse instead of eagle).
  • Implement a pair of tests using allowed_interface.

Michael Howitz added 2 commits May 10, 2019 11:09
* `simple` no longer provides `IPublishTraverse`.
* Accessing an attribute the user is not allowed to now leads to HTTP-401 instead of HTTP-404 (404 came from `zope.browserpage.metaconfigure.simple`)
* In Zope 2 views did not have the possibility to call `publishTraverse` so I removed the test.
@icemac
Copy link
Member

icemac commented May 10, 2019

@dataflake What do you think about the implementation I did together with @sallner?

@dataflake
Copy link
Member Author

@icemac See that lone failure on Python 2 because it renders as u'The eagle has landed'? Is there some doctest way of fixing it? The only reason I did that as doctests, which I otherwise hate, was because all other examples there use it.

@pbauer Can you run the whole Plone unittest battery against this branch? I am sure Plone would exercise all of this a lot more.

I'm fine with any solution that makes all tests succeed. I do want to fix up that non-running doctest that uses PythonScripts, though.

@icemac icemac modified the milestones: 4.0 final, 4.0.1 May 10, 2019
@sallner
Copy link
Member

sallner commented May 10, 2019

After a lengthy discussion over a good lunch, we decided that a 404 should be the correct response in case an attribute is not allowed. This will be resolved after we decided on a good implementation in an upcoming bugfix release.

@dataflake
Copy link
Member Author

I'm fine with pushing this out a little bit, but I'm sure the Plone guys will be unhappy that they basically cannot use 4.0 until that bugfix is out, right?

@sallner
Copy link
Member

sallner commented May 10, 2019

@jensens assured that there are workarounds for all relevant parts and therefore no blocker.

@pbauer
Copy link
Member

pbauer commented May 10, 2019

Plone itself does not use this feature a lot but there are some use-cases that are broken. Among them is enabling ical-upload to a folder: There is a action that enables this with the url:
http://localhost:8080/Plone/folder/@@ical_import_settings/enable

Here ist the registration:

    <browser:page
        for="plone.folder.interfaces.IFolder"
        name="ical_import_settings"
        class=".importer.IcalendarImportSettingsFormView"
        allowed_attributes="enable
                            disable"
        permission="plone.app.event.ImportIcal"
        layer="..interfaces.IBrowserLayer"
        />

In 5.2 this feature is broken so far (it has no tests that use the action...).
With this branch the feature works.

@pbauer
Copy link
Member

pbauer commented May 10, 2019

So: There are cases without workarounds. There are a lot more cases where allowed_attributes and allowed_interfaces appears in Plone's zcml and where features might be broken. Sadly I'm ill so I'm not much help 😞

@dataflake
Copy link
Member Author

@icemac @sallner @pbauer @jaroel Current status: Tests are now passing.

View methods that are not in allowed_attributes cause a 401 now instead of the previous 404.

There's no code in Zope itself that uses allowed_interfaces so I can't be sure if that's fixed.

@d-maurer
Copy link
Contributor

d-maurer commented May 11, 2019 via email

@pbauer
Copy link
Member

pbauer commented May 20, 2019

With plone/plone.app.event#306 this still results in a 404:

Error in test test_enable_ical_import (plone.app.event.tests.test_ical_import.TestICALImportSettings)
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Users/pbauer/workspace/plip/src/plone.app.event/plone/app/event/tests/test_ical_import.py", line 58, in test_enable_ical_import
    self.browser.open(f1_url + '/ical_import_settings/enable')
  File "/Users/pbauer/.cache/buildout/eggs/zope.testbrowser-5.3.2-py3.7.egg/zope/testbrowser/browser.py", line 251, in open
    self._processRequest(url, make_request)
  File "/Users/pbauer/.cache/buildout/eggs/zope.testbrowser-5.3.2-py3.7.egg/zope/testbrowser/browser.py", line 275, in _processRequest
    resp = make_request(reqargs)
  File "/Users/pbauer/.cache/buildout/eggs/zope.testbrowser-5.3.2-py3.7.egg/zope/testbrowser/browser.py", line 249, in make_request
    return self.testapp.get(url, **args)
  File "/Users/pbauer/.cache/buildout/eggs/WebTest-2.0.33-py3.7.egg/webtest/app.py", line 335, in get
    expect_errors=expect_errors)
  File "/Users/pbauer/.cache/buildout/eggs/zope.testbrowser-5.3.2-py3.7.egg/zope/testbrowser/browser.py", line 94, in do_request
    expect_errors)
  File "/Users/pbauer/.cache/buildout/eggs/WebTest-2.0.33-py3.7.egg/webtest/app.py", line 628, in do_request
    res = req.get_response(app, catch_exc_info=True)
  File "/Users/pbauer/.cache/buildout/eggs/WebOb-1.8.5-py3.7.egg/webob/request.py", line 1310, in send
    application, catch_exc_info=True)
  File "/Users/pbauer/.cache/buildout/eggs/WebOb-1.8.5-py3.7.egg/webob/request.py", line 1278, in call_application
    app_iter = application(self.environ, start_response)
  File "/Users/pbauer/.cache/buildout/eggs/WebTest-2.0.33-py3.7.egg/webtest/lint.py", line 201, in lint_app
    iterator = application(environ, start_response_wrapper)
  File "/Users/pbauer/.cache/buildout/eggs/plone.testing-7.0.1-py3.7.egg/plone/testing/_z2_testbrowser.py", line 38, in wrapped_func
    return func(*args, **kw)
  File "/Users/pbauer/.cache/buildout/eggs/plone.testing-7.0.1-py3.7.egg/plone/testing/_z2_testbrowser.py", line 65, in __call__
    wsgi_result = publish(environ, start_response)
  File "/Users/pbauer/workspace/plip/src/Zope/src/ZPublisher/WSGIPublisher.py", line 337, in publish_module
    response = _publish(request, new_mod_info)
  File "/Users/pbauer/workspace/plip/src/Zope/src/ZPublisher/WSGIPublisher.py", line 243, in publish
    obj = request.traverse(path, validated_hook=validate_user)
  File "/Users/pbauer/workspace/plip/src/Zope/src/ZPublisher/BaseRequest.py", line 540, in traverse
    return response.notFoundError(URL)
  File "/Users/pbauer/workspace/plip/src/Zope/src/ZPublisher/HTTPResponse.py", line 986, in notFoundError
    raise exc
zExceptions.NotFound: http://nohost/plone/f1/ical_import_settings/enable

* Require a permission on the view so security has to do its work
* add a test for an attribute which is not allowed explicitly

The first test currently fails because `guarded_getattr` requires an authorized
user. Which is not the case during traversal.
@icemac
Copy link
Member

icemac commented May 20, 2019

The problem is that authentication is done after traversal. So guarded_getattr always raises an Unauthorized for all attributes which are not accessible by Anonymous.

I also tried to move the guarded_getattr call to a post_traverse hook. But this does not help either because these hooks are called after authorisation. In a case of an Unauthorized they are not called at all. This way it is not possible to convert the Unauthorized of a method which is not mentioned in allowed_attributes to a NotFound.

Currently I am low on ideas and out of time. The next step could be to find out, how it ever worked before the changes in 9bc2e7f...da088f0.

I adapted the test so it fails quite the same way as it does in plone.app.event.

@davisagli
Copy link
Member

@icemac I think that the way it worked in Zope 2.13 is that DefaultPublishTraverse would find the attribute via getattr, but it would fail access control validation so return a 401.

Why are you intent on returning a 404 rather than a 401 when an attribute is not allowed? That's not consistent with what the Zope publisher does in other cases when an object is traversable but not authorized. And as you've found, the way that the publisher is structured makes it impossible to customize traversal based on the user's access.

If the 404 is critical, you might be able to achieve it by making traversal return a DeferredAttrAuthProxy like this:

class DeferredAttrAuthProxy(object):
    def __init__(self, context, name):
        self.context = context
        self.name = name

    def __call__(self, *args, **kw):
        try:
            attr = guarded_getattr(self.context, self.name)
        except (AttributeError, Unauthorized):
            raise NotFound(self.name)
        return attr(*args, **kw)

Make sure that DeferredAttrAuthProxy is declared as public (I forget the best way to do this.) Then make SimplePublishTraverse.publishTraverse do return DeferredAttrAuthProxy(self.context, name). This way traversal returns an object that is public but that will check for access and delegate to the intended attribute when called.

@icemac
Copy link
Member

icemac commented May 27, 2019

@davisagli Thank you for investigating this issue. I implemented your suggestion in this PR.
@pbauer Could you please check the current status of the PR against Plone?

@pbauer
Copy link
Member

pbauer commented May 27, 2019

Will do so asap

pbauer added a commit to plone/buildout.coredev that referenced this pull request May 27, 2019
@pbauer
Copy link
Member

pbauer commented May 27, 2019

This breaks a lot of tests and features. Enabling ical-support now works but the a CSRF-check does not kick where it should.

Among the broken features with tests are:

See https://jenkins.plone.org/job/pull-request-5.2/239/ (python2.7) and https://jenkins.plone.org/job/pull-request-5.2-3.7/213/ (python 3.7) for the test-results.

@MatthewWilkes
Copy link
Member

David's approach looks sensible to me, but I very much agree with his point that it's not normal for traversing to a view that you fail a permissions check for to issue a 404.

David's approach is very much like my one in #397 except that he creates a wrapper object where I mutate the simple(...). I've tried my suggested change and the content rules, portlets and template change all work. It does have a CSRF error on ical changes, I'm not sure if that's correct or not.

I've pushed my changes to zope.browserpage to a branch (wilkes/397-allowed-attributes) and would have created a PR to demonstrate them but I also have to add the whitelist setter line to Products.Five.browser.metaconfigure.page and that involves creating a Zope branch but branching from master caused a version issue for me and I'm not sure where to go from.

@MatthewWilkes
Copy link
Member

Okay, I worked out how to build it. Sorry, I'm out of practice with the coredev buildout.

I've got a branch of Plone building at plone/buildout.coredev#588 that includes @pbauer's plone.app.event and my changes to zope.browserpage and Zope core. I don't know if this will work, or if it's the route you'd want to take, but hopefully it's useful information.

@d-maurer
Copy link
Contributor

d-maurer commented May 28, 2019 via email

@icemac
Copy link
Member

icemac commented May 28, 2019

In #642 I created a variant which does not insist to have a HTTP-404 for view attributes which are not in allowed_attributes but this leads to HTTP-401 there. Maybe this is the easiest solution which additionally allows to use custom traversal adapters.

@icemac
Copy link
Member

icemac commented May 28, 2019

@MatthewWilkes wrote:

I've got a branch of Plone building at plone/buildout.coredev#588 that includes @pbauer's plone.app.event and my changes to zope.browserpage and Zope core. I don't know if this will work, or if it's the route you'd want to take, but hopefully it's useful information.

Cool to see an approach which seems to work. I am not sure whether I like the necessary changes in zope.browserpage. If #642 does not work or if we could not agree on having HTTP-401 for disallowed attributes, I'd take this route.

@pbauer
Copy link
Member

pbauer commented May 28, 2019

#642 has the same problems as this one with portlets, content-rules and changing templates.

The approach by @MatthewWilkes (ab64f98 plus zopefoundation/zope.browserpage@f8dedc9) seems to fix all features and tests in Python 3. In Python 2.7 there is the issue that enabling ical-support raises TypeError: unbound method __call__() must be called with simple instance as first argument (got nothing instead). See plone/buildout.coredev#588 (comment) for the full traceback.

Forgive me for not having a opinion regarding 404 vs. 401 or implementation-details at this point. I only focus on finishing Plone 5.2

@jensens
Copy link
Member

jensens commented May 28, 2019

I lost a bit track here about implementation details.
My opinion to 404 vs 401: I just can repeat what I tried to say in our lunch-discussion in Halle at Saltlabs sprint:

  1. If the attribute is defined at the interface or given in allowed attributes, but the user has no permission to access it, it is a 401.
  2. If the attribute exists on the view, but does not exist at the interface or is not given in allowed attributes it has to be a 404.

my 0.02€

@MatthewWilkes
Copy link
Member

@icemac The reason I was keen to do the bulk of this in zope.browserpage is because that is where both the code to support allowed_attributes and the code that prevents traversing to it is. My main complaint is that it involves making changes to Zope because of the duplicated metaconfigure.

I can see why you'd be a bit concerned about the __call__ mutation, but I'm thinking of this as effectively allowing users to change the __page_attribute__ on the fly through traversal, rather than traversing to a new view. The change to __call__ is there because so many users write their own __call__ rather than index.html or whatever the other default methods are.

@icemac
Copy link
Member

icemac commented May 29, 2019

@MatthewWilkes If zope.browserpage is the best place to change the behaviour we should go for it. So this PR can be closed after #643 was successfully merged. I already ported the newly written tests there.

@jensens The approach in #643 seems to implement the behaviour you are expecting.

pbauer added a commit to plone/buildout.coredev that referenced this pull request Jun 15, 2019
@icemac
Copy link
Member

icemac commented Jun 18, 2019

Superseded by #643.

@icemac icemac closed this Jun 18, 2019
@icemac icemac deleted the issue_397 branch June 18, 2019 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BrowserViews implement IPublishTraverse in Zope 4 which breaks allowed_attributes and allowed_interface
9 participants