plonezopedexterity

Plone/dexterity- In an event, how can I change the ownership role of a content type object that only an owner has view permission for?


I'm trying to change the owner of a content type to change after it has been moved in an event subscriber (IObjectAddedEvent).

For my example, I'll refer to the content type as a Contract. When a Contract is created, its moved into a container

There are two types of user roles I have: Contractor and ContractManager

There is a field in a Contract called 'contractor', which is a choice for all users with the Contractor role. A ContractManager is also a Contractor. Only a ContractManager can change the contractor field, otherwise when a Contractor tries to create a Contract, only their name will appear. Also, the ContractManager can't edit it once, its been created, unless the ContractManager happens to be the Contractor/Owner of that Contract.

There is a workflow I created for the Contract and in the workflow, I made it so that only users with the Owner role have the view permission.

In my events.py file that I use for event subscribers, I have this:

@grok.subscriber(Contract, IObjectAddedEvent)
def contractAdded(obj, event):
    #generate id of container contract will be moved into when its created
    #create a container (representing a week, i.e. contracts_12_5_2015)
    #if it doesn't already exist
    #copy and paste contract into container
    moved_contract = contracts[contract_container_id][obj.id]
    changeOwnership(moved_contract, obj.contractor)


def changeOwnership(user_id, obj):
    aq_base(obj).__ac_local_roles_block__ = True
    userid = u"%s" % userid
    membership = getToolByName(obj, 'portal_membership')
    user = membership.getMemberById(userid)
    obj.changeOwnership(user)
    obj.setCreators(userid,)
    owners = [o for o in obj.users_with_local_role('Owner')]
    for owner in owners:
        roles = list(obj.get_local_roles_for_userid(owner))
        roles.remove('Owner')
        if roles:
            obj.manage_setLocalRoles(owner, roles)
        else:
            obj.manage_delLocalRoles([owner])
    roles = list(obj.get_local_roles_for_userid(userid))
    if 'Owner' not in roles:
        roles.append('Owner')
        obj.manage_setLocalRoles(userid, roles)
    obj.reindexObjectSecurity()

Unfortunately, when I am logged in as a ContractManager and change who the contractor is, I get this error:

Traceback (innermost last):
Module ZPublisher.Publish, line 138, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 48, in call_object
Module Solgema.fullcalendar.browser.dx, line 68, in __call__
Module plone.z3cform.layout, line 50, in update
Module plone.dexterity.browser.add, line 117, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 145, in update
Module plone.app.z3cform.csrf, line 21, in execute
Module z3c.form.action, line 98, in execute
Module z3c.form.button, line 315, in __call__
Module z3c.form.button, line 170, in __call__
Module plone.dexterity.browser.add, line 100, in handleAdd
Module z3c.form.form, line 250, in createAndAdd
Module plone.dexterity.browser.add, line 79, in add
AttributeError: 'NoneType' object has no attribute 'id'

I'm assuming the logged in user needs the View Permission for reindexObjectSecurity to work. When I went into the workflow manager (through ZMI) and added the View Permission for the ContractManager, the owner successfully changed, but in my case I'm trying to make it so that only the Owner can see in the Navigation. If that's the case is there a way to get pass this issue?


Solution

  • You may change the SecurityManager during this action using a context manager.

    This way you can run the some specific lines of code as a different user, which has the necessary permissions.

    This example context manager runs code as system user:

    import AccessControl
    
    
    class SwitchedToSystemUser(object):
        """Switch temp. to System user
        """
    
        def __init__(self):
            self._original_security = None
    
        def __enter__(self):
            assert self._original_security is None
    
            self._original_security = AccessControl.getSecurityManager()
    
            _system_user = AccessControl.SecurityManagement.SpecialUsers.system
            AccessControl.SecurityManagement.newSecurityManager(None, _system_user)
    
        def __exit__(self, _exc_type, _exc_value, _traceback):
            AccessControl.SecurityManagement.setSecurityManager(
                self._original_security)
            self._original_security = None
    

    Example usage:

    // CODE FOR LOGGED-IN USER
    ...
    
    
    with SwitchedToSystemUser():
        // DO STUFF AS SYSTEM USER
    
    
    // MORE CODE FOR LOGGED-IN USER
    

    You may also check plone.api.env, which provides context managers to adopt a user or roles.