Plone 5.2 and Ldap/AD

This page "plone-5-2-the-future-proofing-release" indicates that plone 5.2 has LDAP as Feature ready-to use...

In a clean install of plone 5.2, i didn´t find any view to configure ldap/ad.

I have tryed to install plone.app.ldap, but zinstance do not run and show me this error:

/opt/plone/buildout-cache/eggs/Products.CMFUid-3.0.1-py3.6.egg/Products/CMFUid/UniqueIdHandlerTool.py:24: DeprecationWarning: InitializeClass is deprecated. Please import from AccessControl.class_init.
from App.class_init import InitializeClass
/opt/plone/buildout-cache/eggs/Products.CMFEditions-3.3.2-py3.6.egg/Products/CMFEditions/StandardModifiers.py:46: DeprecationWarning: ComponentLookupError is deprecated. Import from zope.interface.interfaces
from zope.component.interfaces import ComponentLookupError
/opt/plone/buildout-cache/eggs/Products.CMFFormController-4.1.0-py3.6.egg/Products/CMFFormController/ControllerPythonScript.py:41: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
2019-11-21 16:56:53,921 WARNING [Init:89][MainThread] Class Products.CMFFormController.ControllerPythonScript.ControllerPythonScript has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs'
2019-11-21 16:56:53,926 WARNING [Init:89][MainThread] Class Products.CMFFormController.ControllerValidator.ControllerValidator has a security declaration for nonexistent method 'ZPythonScriptHTML_changePrefs'
/opt/plone/buildout-cache/eggs/Products.CMFPlacefulWorkflow-2.0.0-py3.6.egg/Products/CMFPlacefulWorkflow/permissions.py:5: DeprecationWarning: setDefaultRoles is deprecated. Please use addPermission from AccessControl.Permission.
from Products.CMFCore.permissions import setDefaultRoles
Traceback (most recent call last):
File "/opt/plone/zinstance/parts/instance/bin/interpreter", line 287, in
exec(compile(__file__f.read(), file, "exec"))
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/serve.py", line 252, in
sys.exit(main() or 0)
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/serve.py", line 248, in main
return command.run()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/serve.py", line 189, in run
global_conf=vars)
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/serve.py", line 217, in loadapp
return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 253, in loadapp
return loadobj(APP, uri, name=name, **kw)
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 278, in loadobj
return context.create()
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 715, in create
return self.object_type.invoke(self)
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 209, in invoke
app = context.app_context.create()
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 715, in create
return self.object_type.invoke(self)
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/loadwsgi.py", line 152, in invoke
return fix_call(context.object, context.global_conf, **context.local_conf)
File "/opt/plone/buildout-cache/eggs/PasteDeploy-2.0.1-py3.6.egg/paste/deploy/util.py", line 55, in fix_call
val = callable(*args, **kw)
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/run.py", line 71, in make_wsgi_app
starter.prepare()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/starter.py", line 41, in prepare
self.startZope()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/Startup/starter.py", line 98, in startZope
Zope2.startup_wsgi()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/init.py", line 50, in startup_wsgi
_startup()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/Zope2/App/startup.py", line 87, in startup
OFS.Application.import_products()
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/OFS/Application.py", line 380, in import_products
import_product(product_dir, product_name)
File "/opt/plone/buildout-cache/eggs/Zope-4.1.1-py3.6.egg/OFS/Application.py", line 390, in import_product
global_dict, global_dict, ('doc', ))
File "/opt/plone/buildout-cache/eggs/Products.LDAPMultiPlugins-1.14-py3.6.egg/Products/LDAPMultiPlugins/init.py", line 22, in
from Products.LDAPMultiPlugins.LDAPMultiPlugin import addLDAPMultiPluginForm
File "/opt/plone/buildout-cache/eggs/Products.LDAPMultiPlugins-1.14-py3.6.egg/Products/LDAPMultiPlugins/LDAPMultiPlugin.py", line 303
raise ValueError, 'Exact Match requested but no id provided'
^
SyntaxError: invalid syntax

So please, i need some help! Thanks!

You'll want to use https://pypi.org/project/pas.plugins.ldap/ instead of plone.app.ldap

I've seen multiple threads about what LDAP addon to use and I don't see that there is a clear answer on this.

While pas.plugins.ldap indeed works on Plone 5.2.x it seems upon cursory review that it doesn't allow for multiple AD servers to be used nor is it support for Groups to be robust enough for our use cases.

Since we're centralizing LDAP support around one addon (that's a def good idea IMHO) those who depend/need this functionality should use the new add-on's issue tracker to make sure functionality needed for enterprise is incorporated as development goes forward.

that it doesn't allow for multiple AD servers to be used

By default only one PAS-Plugin for AD/LDAP is added, and the Plone control panel only configures the default one (acl_users/pasldap). But in ZMI you can manually add more plugins. I have a couple of projects where we use 2 or more LDAP connections, in one case we customized the code so that one connection also supports writing to LDAP for a subset of users.

What exactly is failing for you?

I have a couple of projects where we use 2 or more LDAP connections, in one case we customized the code so that one connection also supports writing to LDAP for a subset of users.

Does this also work as a work around for adding multiple LDAP servers as a fail over? so you can just duplicate the pasldap object and change the ldap connection to a second server and if the first ldap server doesn't respond in time? An issue could be that the timeouts are too long and/or cannot be configured.

@jensens improved pas.plugins.ldap error logging where timeouts are also involved (and I promised to test this branch but haven't yet :frowning: ) in logging improvements by jensens · Pull Request #82 · collective/pas.plugins.ldap · GitHub . There is no environment variable yet to control the generic timeout, see the thread. Changes are on master but not released yet on pypi.

Groups support: I see there are also some improvements to group setup and UI robustness which have landed in master.

plone.app.ldap is Python 2 only and will probably never run in Python 3 land due to several dependencies on lower level old packages like LdapUserfolder and Ldapmultiplugins. pas.plugins.ldap latest release runs fine in Plone 5.2 / Python 3.

Is customization difficult to do? I've tried to use our P5.1.x LDAPMultiPlugins configuration as a model for configuring pas.plugins.ldap.We're not currently writing to AD as that's never went well for us, however we do utilize the old LDAP tool to allow users session info to be passed on to various sites with in our Plone setup. For example, we have some P4.x sites that get authentication passed to them from their entry point in P5/Castle.

I'm not sure this works because then you might get a duplicate set of users and groups. In our case we have to separate ou's we use from the same ldap.

No, there are already all PAS methods implemented, but those responsible for writing to LDAP return False. I already thought about adding this functionality to the plugin, but we figured every LDAP is different (schema etc) and UI configuration would be too complex. So it would be easier to extend the ldap plugin and register your own with the specific config you need.

Maybe you can customize the plugin to allow multiple connections (with multiple authentications), and on ldap errors/timeouts you use the other available connections. So overall config stays the same, only connection strings differ, and you only have one PAS plugin.

The underlying node.ext.ldap and its ugm implementation supports writing to LDAP, just pas.plugins.ldap does not support it.

It is a missing feature. python-ldap supports multiple servers:

The uri parameter may be a comma- or whitespace-separated list of URIs containing only the schema, the host, and the port fields. Note that when using multiple URIs you cannot determine to which URI your client gets connected.
ldap LDAP library interface module — python-ldap 3.2.0 documentation
ldap LDAP library interface module — python-ldap 3.2.0 documentation

So somewhere here node.ext.ldap/src/node/ext/ldap/base.py at master · conestack/node.ext.ldap · GitHub and in the configuration (up to pas.plugins.ldap) this has to be considered.

1 Like

Yes, that's what we use.

Good to know. But than this doesn't work for the fail over scenario @fredvd mentioned. In that case I would extend the config to allow a list of servers, where one can choose the active server, and on error/timeout mark a different server as active. Then as admin one has to actively switch back to the main server once the problems have been solved.

What I'm trying to evaluate is if the functionality of pas.plugins.ldap is similar to LDAPMultiPlugin (muli-forest, multi-LDAP servers, role, role enumeration, etc).

I'm having trouble sorting out differences/similarities. Some of these functions seem to be present but others are not, or not exposed.

Do you think it would be a good idea to use the naming convention that LDAPUserFolder/MultiPlugins used for configuration? I find it slightly confusing? evaluating which option translates from the old to the new.

I do not think it translates 100%, just because pas.plugins.ldap was build from the scratch, w/o stacking up again on the from-the-stone-age stack of wrappers.

In fact pas.plugin.ldap builds upon the packages node.ext.ldap and node.ext.ugm (abstract user and group management) which are not related to Plone at all. The PAS plugin "just" bridges the features of those two to PAS interface implementation.

I see what you're saying regarding node.ext.ldap & .ugm (& no relation to that "other" node too). I know we're comparing apples to oranges with this. I think I can have

Here's another question, regarding LDAP plugins. It appears that there isn't support for either Role_Enumeration (enumerateRoles) or Roles (getRolesForPrincipal).

They appear in Active Plugins and when I try to use the getRolesForPrincipal tool my pasLDAP & added LDAP objects aren't appearing there.

node.ext.ldap has full role support, just until now neither one of the pas.plugin.ldap developers needed this feature in Plone, nor were there any contributions (and also nobody paid me or one of the others to integrate it).

So, yes possible. Headache level should be low given one knows LDAP and some Plone/PAS.

:rofl: no worries. we're evaluating if we even need that feature. we set up this many years ago and this function may be superfluous.

I'm on the novice side of the spectrum on programming but I'd like to help perhaps with documentation? testing?