#208: Adapter-Based Local Role Lookup
- Contents
borg.localrole should become a part of the Plone core
- Proposed by
- Alec Mitchell
- Proposal type
- Architecture
- Assigned to release
- State
- completed
Definitions
PAS - Pluggable Authentication System, the mechanism we use for providing configurable authentication, user data, roles, and groups.
PlonePAS - A set of customizations and additional plugins for PAS tailored for Plone's needs.
__ac_local_roles__ - An attribute or method commonly added to persistent objects which returns a mapping of principal ids to roles for that object.
borg.localrole - A python package containing a PAS plugin for local role determination. It uses named adapters providing an ILocalRoleProvider interface for the objects on which local roles should be determined. The plugin provides acquisition of local roles and group support.
Motivation
The current PlonePAS local role plugin is complex and inflexible. It provides features critical to any local role implementation (role acquisition, role acquisition blocking, group support), but it is impossible to reuse those features in other role managers without copying the entire implementation. It also has very few tests (if any), and each of its methods are almost exact duplicates of one another.
As a result of this implementation, objects which need custom local role support have to copy much of the code from the PlonePAS implementation (or one of its predecessors). For example, the Portal Factory needs to obtain local roles from the intended content path. Currently, it overrides the __ac_local_roles__ method with a copy of the mechanism from the PAS local role manager. The result is that it only provides the local roles the default plugin would provide on the intended add path, and not those from any other local role plugins which may be installed. It might make sense for Portal Factory to provide a proper local role plugin, but it would have to be run for every object regardless of whether it was in the factory or not. It would also still have to re-implement much of the same functionality.
Providing a custom local role mechanism can be time consuming and painful with the current PAS implementation. After re-implementing all the basic functionality with new tests, one needs to develop a persistent component and register it as a plugin. This additional complexity is particularly onerous since it's generally unlikely that the plugin itself will store any persistent state. Local role determination is well suited to the use of adapters.
Fortunately, we already have a PAS plugin for local roles which uses adapters for local role determination. It has a simple and clear implementation, and has excellent test coverage. It provides group support, local role acquisition, and local role blocking automatically for all the adapters registered for it. It allows local role adapters to easily be overridden (more specific adapters), or supplemented (via named adapters).
Assumptions
Proposal
There are only three simple steps needed for this PLIP to be implemented:
Include the borg.localrole package in the next Plone release.
Provide an adapter for borg.localrole that implements the functionality of the current default plugin. An example implementation follows, which hopefully demonstrates how much simpler and more testable an adapter based implementation can be:
class DefaultLocalRoleAdapter(object): """Looks at __ac_local_roles__ to find local roles stored persistently on the object""" implements(ILocalRoleProvider) adapts(Interface) @property def _rolemap(self): rolemap = getattr(self.context, '__ac_local_roles__', {}) if callable(rolemap): rolemap = rolemap() return rolemap def getRoles(self, principal_id): rolemap = self._rolemap return local_roles.get(principal_id, []) def getAllRoles(self): return self._rolemap.iteritems()
This will of course require a couple unit tests.
- Add a migration that removes the existing default plugin and adds the new borg.localrole plugin.
Optional step: An adapter to handle the portal_factory role issue is included with borg.local_role, and should probably be enabled. Additionally, this will allow the current __ac_local_roles__ hack in PortalFactory.py to be removed.
Implementation
See proposal
Deliverables
See proposal
Risks
It's likely that adding an adapter lookup, in place of direct attribute lookup, will add a small performance penalty. Role lookups need to be very fast. The adapter registry is, however, quite fast and provides its own internal caching. When multiple local role providers are involved, its very likely that having all the adapters looked up within the same loop in a single PAS plugin will be significantly faster than having multiple persistent plugins each with its own loops for acquisition.
The use of generators in the acquisition calculation and other optimizations already in place in borg.localrole may already make this performance discrepancy negligible. Some benchmarking may be useful, however the benefits are likely to outweigh anything less than a serious performance discrepancy. Caching may provide some benefit, but it is potentially risky, and should not be attempted without some solid benchmarks.
Progress log
Participants
Alec Mitchell
Framework team vote