diff --git a/CHANGES.rst b/CHANGES.rst index 715e307..e3e2713 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,10 +25,6 @@ History - Remove five.globalrequest dependency. [cillianderoiste] - -1.9.0 (unreleased) ------------------- - - Added Spanish translation #132 [macagua] @@ -38,6 +34,19 @@ History - Drop support for Plone 5/ Python < 3.9 [jensens] +- Default user roles configuration implementation. + [sauzher] + + +1.8.3 (2024-11-13) +------------------ + +- Add uninstall profile + [dumitval] +- Fix: use exact_match for searchUsers/searchGroups in getRolesForPrincipal/getPropertiesForUser + to avoid unexpected results + [mamico] + 1.8.2 (2022-10-31) ------------------ diff --git a/src/pas/plugins/ldap/configure.zcml b/src/pas/plugins/ldap/configure.zcml index 6792929..eaab756 100644 --- a/src/pas/plugins/ldap/configure.zcml +++ b/src/pas/plugins/ldap/configure.zcml @@ -8,12 +8,7 @@ i18n_domain="pas.plugins.ldap" > - - - + diff --git a/src/pas/plugins/ldap/defaults.py b/src/pas/plugins/ldap/defaults.py index 425fe48..a069785 100644 --- a/src/pas/plugins/ldap/defaults.py +++ b/src/pas/plugins/ldap/defaults.py @@ -7,7 +7,12 @@ "server.password": "secret", "server.ignore_cert": False, "server.start_tls": False, + "server.tls_cacertfile": None, + "server.tls_cacertdir": None, + "server.tls_clcertfile": None, + "server.tls_clkeyfile": None, "server.page_size": 1000, + "server.roles": ["Member"], "server.conn_timeout": 5, "server.op_timeout": 600, "cache.cache": False, diff --git a/src/pas/plugins/ldap/plonecontrolpanel/configure.zcml b/src/pas/plugins/ldap/plonecontrolpanel/configure.zcml index 31b6f13..f4dba7a 100644 --- a/src/pas/plugins/ldap/plonecontrolpanel/configure.zcml +++ b/src/pas/plugins/ldap/plonecontrolpanel/configure.zcml @@ -18,6 +18,14 @@ directory="profiles/default" /> + + + + + + diff --git a/src/pas/plugins/ldap/plonecontrolpanel/profiles/uninstall/registry.xml b/src/pas/plugins/ldap/plonecontrolpanel/profiles/uninstall/registry.xml new file mode 100644 index 0000000..7e43441 --- /dev/null +++ b/src/pas/plugins/ldap/plonecontrolpanel/profiles/uninstall/registry.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/pas/plugins/ldap/plonecontrolpanel/setuphandlers.py b/src/pas/plugins/ldap/plonecontrolpanel/setuphandlers.py new file mode 100644 index 0000000..75c6996 --- /dev/null +++ b/src/pas/plugins/ldap/plonecontrolpanel/setuphandlers.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from zope.component.hooks import getSite +from pas.plugins.ldap.plugin import LDAPPlugin +import logging + + +logger = logging.getLogger(__name__) + + +TITLE = "LDAP plugin (pas.plugins.ldap)" + + +def _removePlugin(pas, PLUGIN_ID="pasldap"): + installed = pas.objectIds() + if PLUGIN_ID not in installed: + return TITLE + " already uninstalled." + plugin = getattr(pas, PLUGIN_ID) + if not isinstance(plugin, LDAPPlugin): + logger.warning( + "Uninstall aborted. PAS plugin %s is not an LDAPPlugin.", + PLUGIN_ID) + for info in pas.plugins.listPluginTypeInfo(): + interface = info["interface"] + if not interface.providedBy(plugin): + continue + try: + pas.plugins.deactivatePlugin(interface, plugin.getId()) + except KeyError: + # the plugin was not active + pass + pas._delObject(PLUGIN_ID) + logger.info("Removed LDAPPlugin %s from acl_users.", PLUGIN_ID) + + +def uninstall(context): + site = getSite() + pas = site.acl_users + _removePlugin(pas) + \ No newline at end of file diff --git a/src/pas/plugins/ldap/plugin.py b/src/pas/plugins/ldap/plugin.py index bc4488b..8385f9c 100644 --- a/src/pas/plugins/ldap/plugin.py +++ b/src/pas/plugins/ldap/plugin.py @@ -224,7 +224,7 @@ def authenticateCredentials(self, credentials): pw = credentials.get("password") if not (login and pw): return default - logger.debug("credentials: %s" % credentials) + logger.debug("login: %s" % login) users = self.users if not users: return default @@ -442,8 +442,8 @@ def getRolesForPrincipal(self, principal, request=None): users = self.users if not users: return default - if self.enumerateUsers(id=principal.getId()): - return ("Member",) + if self.enumerateUsers(id=principal.getId(), exact_match=True): + return tuple(set(self._ldap_props.roles)) return default @security.private @@ -564,7 +564,9 @@ def getPropertiesForUser(self, user_or_group, request=None): if not isinstance(ugid, str): ugid = ugid.decode("utf-8") try: - if self.enumerateUsers(id=ugid) or self.enumerateGroups(id=ugid): + if self.enumerateUsers(id=ugid, exact_match=True) or self.enumerateGroups( + id=ugid, exact_match=True + ): return LDAPUserPropertySheet(user_or_group, self) except KeyError: pass @@ -690,7 +692,6 @@ def getGroupById(self, group_id): for propfinder_id, propfinder in plugins.listPlugins( pas_interfaces.IPropertiesPlugin ): - data = propfinder.getPropertiesForUser(group, None) if not data: continue @@ -699,7 +700,6 @@ def getGroupById(self, group_id): group._addGroups(pas._getGroupsForPrincipal(group, None, plugins=plugins)) # add roles for rolemaker_id, rolemaker in plugins.listPlugins(pas_interfaces.IRolesPlugin): - roles = rolemaker.getRolesForPrincipal(group, None) if not roles: continue diff --git a/src/pas/plugins/ldap/properties.py b/src/pas/plugins/ldap/properties.py index 4e3cfb3..b50b2ab 100644 --- a/src/pas/plugins/ldap/properties.py +++ b/src/pas/plugins/ldap/properties.py @@ -102,6 +102,7 @@ def fetch(name, default=UNSET): return val props.uri = fetch("server.uri") + if not fetch("server.anonymous"): props.user = fetch("server.user") password = fetch("server.password") @@ -111,12 +112,12 @@ def fetch(name, default=UNSET): props.user = "" props.password = "" props.ignore_cert = fetch("server.ignore_cert") + props.start_tls = fetch('server.start_tls') + props.tls_cacertfile = fetch('server.tls_cacertfile') + props.tls_cacertdir = fetch('server.tls_cacertdir') + props.tls_clcertfile = fetch('server.tls_clcertfile') + props.tls_clkeyfile = fetch('server.tls_clkeyfile') # TODO: later - # props.start_tls = fetch('server.start_tls') - # props.tls_cacertfile = fetch('server.tls_cacertfile') - # props.tls_cacertdir = fetch('server.tls_cacertdir') - # props.tls_clcertfile = fetch('server.tls_clcertfile') - # props.tls_clkeyfile = fetch('server.tls_clkeyfile') # props.retry_max = fetch(at('server.retry_max') # props.retry_delay = fetch('server.retry_delay') props.page_size = fetch("server.page_size") @@ -125,8 +126,11 @@ def fetch(name, default=UNSET): props.cache = fetch("cache.cache") props.memcached = fetch("cache.memcached") props.timeout = fetch("cache.timeout") + + props.roles = fetch("users.roles") # a server wide variable, but related to user + users.baseDN = fetch("users.dn") - # build attrmap from static keys and dynamic keys inputs + # build attrmap from static keys and dynamic keys inputs users.attrmap = odict() users.attrmap.update(fetch("users.aliases_attrmap")) users_propsheet_attrmap = fetch("users.propsheet_attrmap") @@ -242,18 +246,21 @@ def __init__(self, plugin): self.plugin = plugin # XXX: Later - tls_cacertfile = "" - tls_cacertdir = "" - tls_clcertfile = "" - tls_clkeyfile = "" retry_max = 3 retry_delay = 5 uri = propproxy("server.uri") user = propproxy("server.user") + roles = propproxy("server.roles") password = propproxy("server.password") start_tls = propproxy("server.start_tls") ignore_cert = propproxy("server.ignore_cert") + start_tls = propproxy("server.start_tls") + tls_cacertfile = propproxy("server.tls_cacertfile") + tls_cacertdir = propproxy("server.tls_cacertdir") + tls_clcertfile = propproxy("server.tls_clcertfile") + tls_clkeyfile = propproxy("server.tls_clkeyfile") + page_size = propproxy("server.page_size") conn_timeout = propproxy("server.conn_timeout") op_timeout = propproxy("server.op_timeout") diff --git a/src/pas/plugins/ldap/properties.yaml b/src/pas/plugins/ldap/properties.yaml index 4ed979d..131297f 100644 --- a/src/pas/plugins/ldap/properties.yaml +++ b/src/pas/plugins/ldap/properties.yaml @@ -54,6 +54,36 @@ widgets: props: label: i18n:lbl_ignore_certificate_check:Ignore certificate check? help: i18n:help_ignore_certificate_check:If set on authenticate a failing certificate chain check including CA is ignored. + - start_tls: + factory: '#field:checkbox' + value: expr:context.props.start_tls + props: + label: Use TLS connection + help: If set, the connection is upgraded to TLS. + - tls_cacertfile: + factory: '#field:text' + value: expr:context.props.tls_cacertfile + props: + label: Path to CA certificate file for TLS communication (OPT_X_TLS_CACERTFILE) + help: If set, the LDAP server certificate is checked against the CA certificate file. + - tls_cacertdir: + factory: '#field:text' + value: expr:context.props.tls_cacertdir + props: + label: Path to folder with CA certificate files for TLS communication (OPT_X_TLS_CACERTDIR) + help: If set, the LDAP server certificate is checked against the CA certificates in folder. + - tls_clcertfile: + factory: '#field:text' + value: expr:context.props.tls_clcertfile + props: + label: Path to client certificate file for TLS communication (OPT_X_TLS_CERTFILE). Requires tls_clkeyfile + help: If set, the client certificate is sent to the server. + - tls_clkeyfile: + factory: '#field:text' + value: expr:context.props.tls_clkeyfile + props: + label: Path to client certificate key for TLS communication (OPT_X_TLS_KEYFILE). Requires tls_clcertfile + help: If set, the client certificate is sent to the server. - page_size: factory: '#field:number' value: expr:context.props.page_size @@ -100,6 +130,15 @@ widgets: value: expr:context.users.memberOfSupport props: label: i18n:lbl_memberOf_attribute_supported:memberOf attribute supported? + - roles: + factory: '#array' + value: expr:context.props.roles + props: + label: Roles aquired + array.label: Roles acquired + widgets: + - roles: + factory: field:text - recursiveGroups: factory: '#field:checkbox' value: expr:context.users.recursiveGroups diff --git a/tests/testing.py b/tests/testing.py index f7ea0a8..d0f92ca 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -34,6 +34,7 @@ def ldapprops(context): props.uri = ldaptesting.props.uri props.user = ldaptesting.props.user + props.roles = ldaptesting.props.roles props.password = ldaptesting.props.password props.cache = ldaptesting.props.cache props.page_size = ldaptesting.props.page_size