From debe2781a39dd9be839caefdc4ff1b796df2d320 Mon Sep 17 00:00:00 2001 From: alisha17 Date: Jan 02 2018 15:39:57 +0000 Subject: Issue #35 - dsconf automember support Bug Description: Add support for managing automember to dsconf Fix Description: Initial patch which adds AutoMembershipPlugin, AutoMembershipDefinition and AutoMembershipDefinitions classes to plugins.py and adds tests for checking valid scope, valid filter and if user is correctly added to the group. https://pagure.io/lib389/issue/35 Author: Alisha Aneja Review by: ??? --- diff --git a/dirsrvtests/tests/suites/automember_plugin/automember_test.py b/dirsrvtests/tests/suites/automember_plugin/automember_test.py new file mode 100644 index 0000000..b13c1b2 --- /dev/null +++ b/dirsrvtests/tests/suites/automember_plugin/automember_test.py @@ -0,0 +1,139 @@ +import logging +import pytest +import os +import ldap +from lib389.utils import ds_is_older +from lib389._constants import * +from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinition, AutoMembershipDefinitions +from lib389._mapped_object import DSLdapObjects, DSLdapObject +from lib389 import agreement +from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES +from lib389.idm.group import Groups, Group +from lib389.topologies import topology_st as topo +from lib389._constants import DEFAULT_SUFFIX + + +# Skip on older versions +pytestmark = pytest.mark.skipif(ds_is_older('1.3.7'), reason="Not implemented") + +DEBUGGING = os.getenv("DEBUGGING", default=False) +if DEBUGGING: + logging.getLogger(__name__).setLevel(logging.DEBUG) +else: + logging.getLogger(__name__).setLevel(logging.INFO) +log = logging.getLogger(__name__) + + +@pytest.fixture(scope="module") +def automember_fixture(topo, request): + + groups = Groups(topo.standalone, DEFAULT_SUFFIX) + group = groups.create(properties={'cn': 'testgroup'}) + + automemberplugin = AutoMembershipPlugin(topo.standalone) + automemberplugin.enable() + + topo.standalone.restart() + + automember_prop = { + 'cn': 'testgroup_definition', + 'autoMemberScope': 'ou=People,' + DEFAULT_SUFFIX, + 'autoMemberFilter': 'objectclass=*', + 'autoMemberDefaultGroup': group.dn, + 'autoMemberGroupingAttr': 'member:dn', + } + + automembers = AutoMembershipDefinitions(topo.standalone, "cn=Auto Membership Plugin,cn=plugins,cn=config") + + automember = automembers.create(properties=automember_prop) + + return (group, automembers, automember) + + +def test_automemberscope(automember_fixture, topo): + """Test if the automember scope is valid + + :id: c3d3f250-e7fd-4441-8387-3d24c156e982 + :setup: Standalone instance, enabled Auto Membership Plugin + :steps: + 1. Create automember with invalid cn that raises + UNWILLING_TO_PERFORM exception + 2. If exception raised, set scope to any cn + 3. If exception is not raised, set scope to with ou=People + :expectedresults: + 1. Should be success + 2. Should be success + 3. Should be success + """ + + (group, automembers, automember) = automember_fixture + + automember_prop = { + 'cn': 'anyrandomcn', + 'autoMemberScope': 'ou=People,' + DEFAULT_SUFFIX, + 'autoMemberFilter': 'objectclass=*', + 'autoMemberDefaultGroup': group.dn, + 'autoMemberGroupingAttr': 'member:dn', + } + + # depends on issue #49465 + + # with pytest.raises(ldap.UNWILLING_TO_PERFORM): + # automember = automembers.create(properties=automember_prop) + # automember.set_scope("cn=No Entry,%s" % DEFAULT_SUFFIX) + + automember.set_scope("ou=People,%s" % DEFAULT_SUFFIX) + + +def test_automemberfilter(automember_fixture, topo): + """Test if the automember filter is valid + + :id: 935c55de-52dc-4f80-b7dd-3aacd30f6df2 + :setup: Standalone instance, enabled Auto Membership Plugin + :steps: + 1. Create automember with invalid filter that raises + UNWILLING_TO_PERFORM exception + 2. If exception raised, set filter to the invalid filter + 3. If exception is not raised, set filter as all objectClasses + :expectedresults: + 1. Should be success + 2. Should be success + 3. Should be success + """ + + (group, automembers, automember) = automember_fixture + + automember_prop = { + 'cn': 'anyrandomcn', + 'autoMemberScope': 'ou=People,' + DEFAULT_SUFFIX, + 'autoMemberFilter': '(ou=People', + 'autoMemberDefaultGroup': group.dn, + 'autoMemberGroupingAttr': 'member:dn', + } + + with pytest.raises(ldap.UNWILLING_TO_PERFORM): + automember = automembers.create(properties=automember_prop) + automember.set_filter("(ou=People") + + automember.set_filter("objectClass=*") + + +def test_adduser(automember_fixture, topo): + """Test if member is automatically added to the group + + :id: 14f1e2f5-2162-41ab-962c-5293516baf2e + :setup: Standalone instance, enabled Auto Membership Plugin + :steps: + 1. Create a user + 2. Assert that the user is member of the group + :expectedresults: + 1. Should be success + 2. Should be success + """ + + (group, automembers, automember) = automember_fixture + + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + user = users.create(properties=TEST_USER_PROPERTIES) + + assert group.is_member(user.dn) diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 3644193..6161500 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -222,10 +222,10 @@ class DSLdapObject(DSLogging): self._log.debug("%s present(%r) %s" % (self._dn, attr, value)) e = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=[attr, ], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] - if value is None: - return e.hasAttr(attr) - else: - return e.hasValue(attr, value) + values = self.get_attr_vals_bytes(attr) + self._log.debug("%s contains %s" % (self._dn, values)) + + return ensure_bytes(value).lower() in [x.lower() for x in values] def add(self, key, value): """Add an attribute with a value diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py index 9f86ec9..e0080d6 100644 --- a/src/lib389/lib389/plugins.py +++ b/src/lib389/lib389/plugins.py @@ -387,9 +387,114 @@ class AccountUsabilityPlugin(Plugin): super(AccountUsabilityPlugin, self).__init__(instance, dn) class AutoMembershipPlugin(Plugin): + _plugin_properties = { + 'cn' : 'Auto Membership Plugin', + 'nsslapd-pluginEnabled' : 'off', + 'nsslapd-pluginPath' : 'libautomember-plugin', + 'nsslapd-pluginInitfunc' : 'automember_init', + 'nsslapd-pluginType' : 'betxnpreoperation', + 'nsslapd-plugin-depends-on-type' : 'database', + 'nsslapd-pluginId' : 'Auto Membership', + 'nsslapd-pluginVendor' : '389 Project', + 'nsslapd-pluginVersion' : '1.3.7.0', + 'nsslapd-pluginDescription' : 'Auto Membership plugin', + } + def __init__(self, instance, dn="cn=Auto Membership Plugin,cn=plugins,cn=config"): super(AutoMembershipPlugin, self).__init__(instance, dn) +class AutoMembershipDefinition(DSLdapObject): + """A single instance of Auto Membership Plugin entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + """ + + def __init__(self, instance, dn=None): + super(AutoMembershipDefinition, self).__init__(instance, dn) + self._rdn_attribute = 'cn' + self._must_attributes = ['cn'] + self._create_objectclasses = ['top', 'AutoMemberDefinition'] + self._protected = False + + def get_groupattr(self): + """Get grouping attributes + + :returns: autoMemberGroupingAttr values + """ + return self.get_attr_vals_utf8('autoMemberGroupingAttr') + + def set_groupattr(self, attr): + """Set grouping attributes + + :param attr: autoMemberGroupingAttr value + :type attr: str + """ + self.set('autoMemberGroupingAttr', attr) + + def get_defaultgroup(self, attr): + """Get default group + + :returns: autoMemberDefaultGroup value + """ + return self.get_attr_vals_utf8('autoMemberDefaultGroup') + + def set_defaultgroup(self, attr): + """Set default group + + :param attr: autoMemberDefaultGroup value + :type attr: str + """ + self.set('autoMemberDefaultGroup', attr) + + def get_scope(self, attr): + """Get scope + + :returns: autoMemberScope value + """ + return self.get_attr_vals_utf8('autoMemberScope') + + def set_scope(self, attr): + """Set scope + + :param attr: autoMemberScope value + :type attr: str + """ + self.set('autoMemberScope', attr) + + def get_filter(self, attr): + """Get filter + + :returns: autoMemberFilter value + """ + return self.get_attr_vals_utf8('autoMemberFilter') + + def set_filter(self, attr): + """Set filter + + :param attr: autoMemberFilter value + :type attr: str + """ + self.set('autoMemberFilter', attr) + +class AutoMembershipDefinitions(DSLdapObjects): + """DSLdapObjects that represents Auto Membership Plugin entry + + :param instance: An instance + :type instance: lib389.DirSrv + :param basedn: Base DN for all account entries below + :type basedn: str + """ + + def __init__(self, instance, basedn): + super(AutoMembershipDefinitions, self).__init__(instance) + self._objectclasses = ['top','autoMemberDefinition'] + self._filterattrs = ['cn'] + self._childobject = AutoMembershipDefinition + self._basedn = basedn + class ContentSynchronizationPlugin(Plugin): def __init__(self, instance, dn="cn=Content Synchronization,cn=plugins,cn=config"): super(ContentSynchronizationPlugin, self).__init__(instance, dn)