Call view within view but in the context of an other user

I am using Pone 5.2.2 and want to achieve the following:
I've created a view every user can call (permission="zope.Public"). That view retrieves some data through a GET request. Dependent on its value I want to call a different view in the context of an other user. The view to be called has the form of an usual HTTP request but the path is relative to the portal root.

Assume I want to call the view http://localhost:8080/mysite/secret.png/@@download in the context of the user "nicolas" but the original call is just an anonymous user with right GET request. How can I respond with secret.png as long as nicolas has the right permissions to see it?

I already tried this:

from plone import api
from AccessControl import ClassSecurityInfo, getSecurityManager
from AccessControl.SecurityManagement import newSecurityManager, setSecurityManager
from AccessControl.User import Super

class UnrestrictedUser(Super):
    """Unrestricted user that still has an id.
    """
    def getId(self):
        """Return the ID of the user.
        """
        return self.getUserName()

def executeAsUser(user, function, *args, **kwargs):
    sm = getSecurityManager()
    
    portal = api.portal.get()

    try:
        try:
            tmp_user = UnrestrictedUser(
                sm.getUser().getId(), '', api.user.get_roles(user = user), ''
                )

            # Wrap the user in the acquisition context of the portal
            tmp_user = tmp_user.__of__(portal.acl_users)
            newSecurityManager(None, tmp_user)

            # Call the function
            return function(*args, **kwargs)

        except:
            # If special exception handlers are needed, run them here
            raise
    finally:
        # Restore the old security manager
        setSecurityManager(sm)

Within the __call__ method of my view I then write this:

user = api.user.get(userid = 'nicolas')
if not user:
    self.request.response.setStatus(401, reason = "Unauthorized. Access denied.", lock = True)
    return ''

portal = api.portal.get()
return executeAsUser(
    user,
    lambda x: portal.restrictedTraverse(x).__call__(),
    'secret.png/@@download')

But this does not seem to work. I always get redirected to the login_form. I also tried an unrestrictedTraverse() without the executeAsUser but it also redirected me to the login form again.

In principle I want to make the outer view transparent to the user. I should simply call the other view in the context of a given user.

There's https://docs.plone.org/develop/plone.api/docs/api/env.html#plone.api.env.adopt_user that you use to switch user and then do a traverse etc.

I'm a bit confused on why you would want this. Unless you explicitly need the user nicolas to end up in Z2 logs, I don't see why you cant simply allow View for Anonymous.

You also can use portal.unrestrictedTraverse to get to the object?

Are you aware that the code inside a view is trusted? This means it can do anything unless this is explicitly protected (via calls to checkPermisison or validate).

checkPermission and validate check against the current user. To perform checks for a different user, you can either switch the user (with newSecurityManager) or resolve the permissions into the roles granted this permission in the current context and then verify that the user has one of those roles.

If your purpose is not to check permissions for a different user but to allow a user to perform an operation (in a controlled context) for which he usually has not sufficient permission, you could use so called "proxy roles". You can setup proxy roles easily with dm.zopepatches.security.

1 Like

The idea behind that is that in the beginning Plone generates a temporary link which contains data which is secured using encryption and/or a signature. This link can be sent to anywhere, also to an other machine which knows absolutely nothing but that there is some data behind that link. If that link gets called Plone decrypts it and may has to impersonate a given user to access the requested data. This is just a second barrier where an admin could restrict access to the given object in between the creation of the link and when it finally gets opened.

That sounds promising. I will give it a try.

Even that was not possible and I don't know why.

I was not entirely aware of that. I remember I had to use the executeAsUser method to call the plone.api.content to move some objects around. But maybe these calls are protected by checkPermission/validate then.

Indeed. Moving, copying and (I think) deletion are some rare examples of functions with explicit internal security checks.

Interestingly this works:

with api.env.adopt_roles(['Manager']):
    return portal.restrictedTraverse(path).__call__()

But this does not:

return portal.unrestrictedTraverse(path).__call__()

I guess that the __call__() makes the difference here. if you just want the object and change things unregistrictedTraverse() is enough.