This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch master
in repository lib389.
commit 33534a011193389f77653e2ece6f98ab4fe48ae6
Author: Ilias Stamatis <stamatis.iliass(a)gmail.com>
Date: Tue Aug 15 20:48:57 2017 +0300
Issue 43 - Add support for Referential Integrity plugin
Description: Add dsconf support for configuring the Referential Integrity
plugin from the command line, along with functional tests for testing its
functionality.
https://pagure.io/lib389/issue/43
Author: Ilias95
Review by: wibrown (Thanks!)
---
cli/dsconf | 2 +
lib389/cli_conf/plugins/referint.py | 197 +++++++++++++++++++++++++
lib389/plugins.py | 103 ++++++++++++-
lib389/tests/cli/conf_plugins/referint_test.py | 119 +++++++++++++++
lib389/tests/plugins/referint_test.py | 83 +++++++++++
5 files changed, 497 insertions(+), 7 deletions(-)
diff --git a/cli/dsconf b/cli/dsconf
index 73d12a2..ea1d056 100755
--- a/cli/dsconf
+++ b/cli/dsconf
@@ -26,6 +26,7 @@ from lib389.cli_conf.plugins import memberof as cli_memberof
from lib389.cli_conf.plugins import usn as cli_usn
from lib389.cli_conf.plugins import rootdn_ac as cli_rootdn_ac
from lib389.cli_conf.plugins import whoami as cli_whoami
+from lib389.cli_conf.plugins import referint as cli_referint
from lib389.cli_base import disconnect_instance, connect_instance
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
@@ -71,6 +72,7 @@ if __name__ == '__main__':
cli_usn.create_parser(subparsers)
cli_rootdn_ac.create_parser(subparsers)
cli_whoami.create_parser(subparsers)
+ cli_referint.create_parser(subparsers)
args = parser.parse_args()
diff --git a/lib389/cli_conf/plugins/referint.py b/lib389/cli_conf/plugins/referint.py
new file mode 100644
index 0000000..9d52b89
--- /dev/null
+++ b/lib389/cli_conf/plugins/referint.py
@@ -0,0 +1,197 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016-2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import ldap
+
+from lib389.plugins import ReferentialIntegrityPlugin
+from lib389.cli_conf.plugin import add_generic_plugin_parsers
+
+
+def manage_update_delay(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ if args.value is None:
+ val = plugin.get_update_delay_formatted()
+ log.info(val)
+ else:
+ plugin.set_update_delay(args.value)
+ log.info('referint-update-delay set to
"{}"'.format(args.value))
+
+def display_membership_attr(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ log.info(plugin.get_membership_attr_formatted())
+
+def add_membership_attr(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.add_membership_attr(args.value)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ log.info('Value "{}" already exists.'.format(args.value))
+ else:
+ log.info('successfully added membership attribute
"{}"'.format(args.value))
+
+def remove_membership_attr(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.remove_membership_attr(args.value)
+ except ldap.OPERATIONS_ERROR:
+ log.error("Error: Failed to delete. At least one value for membership
attribute should exist.")
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.error('Error: Failed to delete. No value "{0}"
found.'.format(args.value))
+ else:
+ log.info('successfully removed membership attribute
"{}"'.format(args.value))
+
+def display_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ val = plugin.get_entryscope_formatted()
+ if not val:
+ log.info("nsslapd-pluginEntryScope is not set")
+ else:
+ log.info(val)
+
+def add_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.add_entryscope(args.value)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ log.info('Value "{}" already exists.'.format(args.value))
+ else:
+ log.info('successfully added nsslapd-pluginEntryScope value
"{}"'.format(args.value))
+
+def remove_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.remove_entryscope(args.value)
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.error('Error: Failed to delete. No value "{0}"
found.'.format(args.value))
+ else:
+ log.info('successfully removed nsslapd-pluginEntryScope value
"{}"'.format(args.value))
+
+def remove_all_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ plugin.remove_all_entryscope()
+ log.info('successfully removed all nsslapd-pluginEntryScope values')
+
+def display_excludescope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ val = plugin.get_excludescope_formatted()
+ if not val:
+ log.info("nsslapd-pluginExcludeEntryScope is not set")
+ else:
+ log.info(val)
+
+def add_excludescope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.add_excludescope(args.value)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ log.info('Value "{}" already exists.'.format(args.value))
+ else:
+ log.info('successfully added nsslapd-pluginExcludeEntryScope value
"{}"'.format(args.value))
+
+def remove_excludescope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.remove_excludescope(args.value)
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.error('Error: Failed to delete. No value "{0}"
found.'.format(args.value))
+ else:
+ log.info('successfully removed nsslapd-pluginExcludeEntryScope value
"{}"'.format(args.value))
+
+def remove_all_excludescope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ plugin.remove_all_excludescope()
+ log.info('successfully removed all nsslapd-pluginExcludeEntryScope values')
+
+def display_container_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ val = plugin.get_container_scope_formatted()
+ if not val:
+ log.info("nsslapd-pluginContainerScope is not set")
+ else:
+ log.info(val)
+
+def add_container_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.add_container_scope(args.value)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ log.info('Value "{}" already exists.'.format(args.value))
+ else:
+ log.info('successfully added nsslapd-pluginContainerScope value
"{}"'.format(args.value))
+
+def remove_container_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ try:
+ plugin.remove_container_scope(args.value)
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.error('Error: Failed to delete. No value "{0}"
found.'.format(args.value))
+ else:
+ log.info('successfully removed nsslapd-pluginContainerScope value
"{}"'.format(args.value))
+
+def remove_all_container_scope(inst, basedn, log, args):
+ plugin = ReferentialIntegrityPlugin(inst)
+ plugin.remove_all_container_scope()
+ log.info('successfully removed all nsslapd-pluginContainerScope values')
+
+
+def create_parser(subparsers):
+ referint_parser = subparsers.add_parser('referint', help='Manage and
configure Referential Integrity plugin')
+
+ subcommands = referint_parser.add_subparsers(help='action')
+
+ add_generic_plugin_parsers(subcommands, ReferentialIntegrityPlugin)
+
+ delay_parser = subcommands.add_parser('delay', help='get or set update
delay')
+ delay_parser.set_defaults(func=manage_update_delay)
+ delay_parser.add_argument('value', nargs='?', help='The value to
set as update delay')
+
+ attr_parser = subcommands.add_parser('attrs', help='get or manage
membership attributes')
+ attr_parser.set_defaults(func=display_membership_attr)
+ attr_subcommands = attr_parser.add_subparsers(help='action')
+ add_attr_parser = attr_subcommands.add_parser('add', help='add membership
attribute')
+ add_attr_parser.set_defaults(func=add_membership_attr)
+ add_attr_parser.add_argument('value', help='membership attribute to
add')
+ del_attr_parser = attr_subcommands.add_parser('del', help='remove
membership attribute')
+ del_attr_parser.set_defaults(func=remove_membership_attr)
+ del_attr_parser.add_argument('value', help='membership attribute to
remove')
+
+ scope_parser = subcommands.add_parser('scope', help='get or manage
referint scope')
+ scope_parser.set_defaults(func=display_scope)
+ scope_subcommands = scope_parser.add_subparsers(help='action')
+ add_scope_parser = scope_subcommands.add_parser('add', help='add entry
scope value')
+ add_scope_parser.set_defaults(func=add_scope)
+ add_scope_parser.add_argument('value', help='The value to add in referint
entry scope')
+ del_scope_parser = scope_subcommands.add_parser('del', help='remove entry
scope value')
+ del_scope_parser.set_defaults(func=remove_scope)
+ del_scope_parser.add_argument('value', help='The value to remove from
entry scope')
+ delall_scope_parser = scope_subcommands.add_parser('delall', help='remove
all entry scope values')
+ delall_scope_parser.set_defaults(func=remove_all_scope)
+
+ exclude_parser = subcommands.add_parser('exclude', help='get or manage
referint exclude scope')
+ exclude_parser.set_defaults(func=display_excludescope)
+ exclude_subcommands = exclude_parser.add_subparsers(help='action')
+ add_exclude_parser = exclude_subcommands.add_parser('add', help='add
exclude scope value')
+ add_exclude_parser.set_defaults(func=add_excludescope)
+ add_exclude_parser.add_argument('value', help='The value to add in
exclude scope')
+ del_exclude_parser = exclude_subcommands.add_parser('del', help='remove
exclude scope value')
+ del_exclude_parser.set_defaults(func=remove_excludescope)
+ del_exclude_parser.add_argument('value', help='The value to remove from
exclude scope')
+ delall_exclude_parser = exclude_subcommands.add_parser('delall',
help='remove all exclude scope values')
+ delall_exclude_parser.set_defaults(func=remove_all_excludescope)
+
+ container_parser = subcommands.add_parser('container', help='get or
manage referint container scope')
+ container_parser.set_defaults(func=display_container_scope)
+ container_subcommands = container_parser.add_subparsers(help='action')
+ add_container_parser = container_subcommands.add_parser('add', help='add
container scope value')
+ add_container_parser.set_defaults(func=add_container_scope)
+ add_container_parser.add_argument('value', help='The value to add in
container scope')
+ del_container_parser = container_subcommands.add_parser('del',
help='remove container scope value')
+ del_container_parser.set_defaults(func=remove_container_scope)
+ del_container_parser.add_argument('value', help='The value to remove from
container scope')
+ delall_container_parser = container_subcommands.add_parser('delall',
help='remove all container scope values')
+ delall_container_parser.set_defaults(func=remove_all_container_scope)
diff --git a/lib389/plugins.py b/lib389/plugins.py
index 61567fe..d683f21 100644
--- a/lib389/plugins.py
+++ b/lib389/plugins.py
@@ -8,6 +8,7 @@
import ldap
import copy
+import os.path
from lib389 import tasks
from lib389._mapped_object import DSLdapObjects, DSLdapObject
@@ -118,23 +119,111 @@ class ManagedEntriesPlugin(Plugin):
# Because there are potentially many MEP configs.
class ReferentialIntegrityPlugin(Plugin):
+ _plugin_properties = {
+ 'cn' : 'referential integrity postoperation',
+ 'nsslapd-pluginEnabled': 'off',
+ 'nsslapd-pluginPath': 'libreferint-plugin',
+ 'nsslapd-pluginInitfunc': 'referint_postop_init',
+ 'nsslapd-pluginType': 'betxnpostoperation',
+ 'nsslapd-pluginprecedence': '40',
+ 'nsslapd-plugin-depends-on-type': 'database',
+ 'referint-update-delay': '0',
+ 'referint-membership-attr': ['member', 'uniquemember',
'owner', 'seeAlso',],
+ 'nsslapd-pluginId' : 'referint',
+ 'nsslapd-pluginVendor' : '389 Project',
+ 'nsslapd-pluginVersion' : '1.3.7.0',
+ 'nsslapd-pluginDescription' : 'referential integrity plugin',
+ }
+
def __init__(self, instance, dn="cn=referential integrity
postoperation,cn=plugins,cn=config", batch=False):
super(ReferentialIntegrityPlugin, self).__init__(instance, dn, batch)
+ self._create_objectclasses.extend(['extensibleObject'])
+ self._must_attributes.extend([
+ 'referint-update-delay',
+ 'referint-logfile',
+ 'referint-membership-attr',
+ ])
self._lint_functions = [self._lint_update_delay]
+ def create(self, rdn=None, properties=None, basedn=None):
+ referint_log = os.path.join(self._instance.ds_paths.log_dir,
"referint")
+ if properties is None:
+ properties = {'referint-logfile': referint_log}
+ else:
+ properties['referint-logfile'] = referint_log
+ return super(ReferentialIntegrityPlugin, self).create(rdn, properties, basedn)
+
def _lint_update_delay(self):
if self.status():
delay = self.get_attr_val_int("referint-update-delay")
if delay is not None and delay != 0:
return DSRILE0001
- # referint-update-delay: 0
- # referint-logfile: /opt/dirsrv/var/log/dirsrv/slapd-standalone_2/referint
- # referint-logchanges: 0
- # referint-membership-attr: member
- # referint-membership-attr: uniquemember
- # referint-membership-attr: owner
- # referint-membership-attr: seeAlso
+ def get_update_delay(self):
+ return self.get_attr_val_int('referint-update-delay')
+
+ def get_update_delay_formatted(self):
+ return self.display_attr('referint-update-delay')
+
+ def set_update_delay(self, value):
+ self.set('referint-update-delay', str(value))
+
+ def get_membership_attr(self, formatted=False):
+ return self.get_attr_vals_utf8('referint-membership-attr')
+
+ def get_membership_attr_formatted(self):
+ return self.display_attr('referint-membership-attr')
+
+ def add_membership_attr(self, attr):
+ self.add('referint-membership-attr', attr)
+
+ def remove_membership_attr(self, attr):
+ self.remove('referint-membership-attr', attr)
+
+ def get_entryscope(self, formatted=False):
+ return self.get_attr_vals_utf8('nsslapd-pluginentryscope')
+
+ def get_entryscope_formatted(self):
+ return self.display_attr('nsslapd-pluginentryscope')
+
+ def add_entryscope(self, attr):
+ self.add('nsslapd-pluginentryscope', attr)
+
+ def remove_entryscope(self, attr):
+ self.remove('nsslapd-pluginentryscope', attr)
+
+ def remove_all_entryscope(self):
+ self.remove_all('nsslapd-pluginentryscope')
+
+ def get_excludescope(self):
+ return self.get_attr_vals_ut8('nsslapd-pluginexcludeentryscope')
+
+ def get_excludescope_formatted(self):
+ return self.display_attr('nsslapd-pluginexcludeentryscope')
+
+ def add_excludescope(self, attr):
+ self.add('nsslapd-pluginexcludeentryscope', attr)
+
+ def remove_excludescope(self, attr):
+ self.remove('nsslapd-pluginexcludeentryscope', attr)
+
+ def remove_all_excludescope(self):
+ self.remove_all('nsslapd-pluginexcludeentryscope')
+
+ def get_container_scope(self):
+ return self.get_attr_vals_ut8('nsslapd-plugincontainerscope')
+
+ def get_container_scope_formatted(self):
+ return self.display_attr('nsslapd-plugincontainerscope')
+
+ def add_container_scope(self, attr):
+ self.add('nsslapd-plugincontainerscope', attr)
+
+ def remove_container_scope(self, attr):
+ self.remove('nsslapd-plugincontainerscope', attr)
+
+ def remove_all_container_scope(self):
+ self.remove_all('nsslapd-plugincontainerscope')
class SyntaxValidationPlugin(Plugin):
def __init__(self, instance, dn="cn=Syntax Validation
Task,cn=plugins,cn=config", batch=False):
diff --git a/lib389/tests/cli/conf_plugins/referint_test.py
b/lib389/tests/cli/conf_plugins/referint_test.py
new file mode 100644
index 0000000..8a31ffc
--- /dev/null
+++ b/lib389/tests/cli/conf_plugins/referint_test.py
@@ -0,0 +1,119 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016-2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import pytest
+
+from lib389.tests.cli import topology as default_topology
+from lib389.cli_base import LogCapture, FakeArgs
+from lib389.plugins import ReferentialIntegrityPlugin
+from lib389.cli_conf.plugins import referint as referint_cli
+
+
+(a)pytest.fixture(scope="module")
+def topology(request):
+ topology = default_topology(request)
+
+ plugin = ReferentialIntegrityPlugin(topology.standalone)
+ if not plugin.exists():
+ plugin.create()
+
+ # we need to restart the server after enabling the plugin
+ plugin.enable()
+ topology.standalone.restart()
+ topology.logcap.flush()
+
+ return topology
+
+
+def test_set_update_delay(topology):
+ args = FakeArgs()
+
+ args.value = 60
+ referint_cli.manage_update_delay(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains('referint-update-delay set to
"60"')
+ topology.logcap.flush()
+
+ args.value = None
+ referint_cli.manage_update_delay(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("referint-update-delay: 60")
+ topology.logcap.flush()
+
+ args.value = 0
+ referint_cli.manage_update_delay(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains('referint-update-delay set to
"0"')
+ topology.logcap.flush()
+
+ args.value = None
+ referint_cli.manage_update_delay(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("referint-update-delay: 0")
+ topology.logcap.flush()
+
+def test_add_membership_attr(topology):
+ args = FakeArgs()
+
+ args.value = "member2"
+ referint_cli.add_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("successfully added membership attribute")
+ topology.logcap.flush()
+
+ referint_cli.display_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains(": member2")
+ topology.logcap.flush()
+
+def test_add_membership_attr_with_value_that_already_exists(topology):
+ plugin = ReferentialIntegrityPlugin(topology.standalone)
+ # setup test
+ if not "uniqueMember" in plugin.get_membership_attr():
+ plugin.add_membership_attr("uniqueMember")
+
+ args = FakeArgs()
+
+ args.value = "uniqueMember"
+ referint_cli.add_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("already exists")
+ topology.logcap.flush()
+
+def test_remove_membership_attr_with_value_that_exists(topology):
+ plugin = ReferentialIntegrityPlugin(topology.standalone)
+ # setup test
+ if not "uniqueMember" in plugin.get_membership_attr():
+ plugin.add_membership_attr("uniqueMember")
+
+ args = FakeArgs()
+
+ args.value = "uniqueMember"
+ referint_cli.remove_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("successfully removed membership
attribute")
+ topology.logcap.flush()
+
+ referint_cli.display_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert not topology.logcap.contains(": uniqueMember")
+ topology.logcap.flush()
+
+def test_remove_membership_attr_with_value_that_doesnt_exist(topology):
+ args = FakeArgs()
+
+ args.value = "whatever"
+ referint_cli.remove_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains('No value "{0}"
found'.format(args.value))
+ topology.logcap.flush()
+
+def test_try_remove_all_membership_attr_values(topology):
+ plugin = ReferentialIntegrityPlugin(topology.standalone)
+ #setup test
+ membership_values = plugin.get_membership_attr()
+ assert len(membership_values) > 0
+ for val in membership_values[:-1]:
+ plugin.remove_membership_attr(val)
+
+ args = FakeArgs()
+
+ args.value = membership_values[-1]
+ referint_cli.remove_membership_attr(topology.standalone, None, topology.logcap.log,
args)
+ assert topology.logcap.contains("Error: Failed to delete. At least one value for
membership attribute should exist.")
+ topology.logcap.flush()
diff --git a/lib389/tests/plugins/referint_test.py
b/lib389/tests/plugins/referint_test.py
new file mode 100644
index 0000000..53500d9
--- /dev/null
+++ b/lib389/tests/plugins/referint_test.py
@@ -0,0 +1,83 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2016-2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+import pytest
+
+from lib389.topologies import topology_st
+from lib389.plugins import ReferentialIntegrityPlugin
+from lib389.tests.plugins.utils import (
+ create_test_user, create_test_group, delete_objects)
+
+
+(a)pytest.fixture(scope="module")
+def plugin(request):
+ return ReferentialIntegrityPlugin(topology_st(request).standalone)
+
+
+def test_referint_enable_disable(plugin):
+ """
+ Test that the plugin doesn't do anything while disabled, and functions
+ properly when enabled.
+
+ NOTICE: This test case leaves the plugin enabled for the following tests.
+ """
+ # assert plugin is disabled (by default)
+ assert plugin.status() == False
+
+ user1 = create_test_user(plugin._instance)
+ group = create_test_group(plugin._instance)
+ group.add_member(user1.dn)
+
+ user1.delete()
+ # assert that user was not removed from group because the plugin is disabled
+ assert group.present(attr='member', value=user1.dn)
+
+ # enable the plugin and restart the server for the action to take effect
+ plugin.enable()
+ plugin._instance.restart()
+ assert plugin.status() == True
+
+ user2 = create_test_user(plugin._instance)
+ group.add_member(user2.dn)
+
+ user2.delete()
+ # assert that user was removed from the group as well
+ assert not group.present(attr='member', value=user2.dn)
+
+ # clean up for subsequent test cases
+ delete_objects([group])
+
+def test_membership_attr(plugin):
+ """
+ Test that the plugin performs integrity updates based on the attributes
+ defined by referint-membership-attr.
+ """
+ # remove a membership attribute
+ plugin.remove_membership_attr('uniquemember')
+
+ user1 = create_test_user(plugin._instance)
+ group = create_test_group(plugin._instance, unique_group=True)
+ group.add_member(user1.dn)
+
+ user1.delete()
+ # assert that the user was not removed from the group
+ assert group.present(attr='uniquemember', value=user1.dn)
+
+ # now put this membership attribute back and try again
+ plugin.add_membership_attr('uniquemember')
+
+ user2 = create_test_user(plugin._instance)
+ group.add_member(user2.dn)
+
+ user2.delete()
+ # assert that user was removed from the group as well
+ assert not group.present(attr='uniquemember', value=user2.dn)
+
+ # clean up for subsequent test cases
+ delete_objects([group])
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.