#50259 Issue 50041 - Add CLI functionality for special plugins
Closed 3 years ago by spichugi. Opened 5 years ago by spichugi.
spichugi/389-ds-base plugin_cli_main  into  master

@@ -0,0 +1,84 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2019 Red Hat, Inc.

+ # All rights reserved.

+ #

+ # License: GPL (version 3 or any later version).

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ #

+ """Test DNA plugin functionality"""

+ 

+ import logging

+ import pytest

+ from lib389._constants import DEFAULT_SUFFIX

+ from lib389.plugins import DNAPlugin, DNAPluginSharedConfigs, DNAPluginConfigs

+ from lib389.idm.organizationalunit import OrganizationalUnits

+ from lib389.idm.user import UserAccounts

+ from lib389.topologies import topology_st

+ import ldap

+ 

+ log = logging.getLogger(__name__)

+ 

+ 

+ @pytest.mark.ds47937

+ def test_dnatype_only_valid(topology_st):

+     """Test that DNA plugin only accepts valid attributes for "dnaType"

+ 

+     :id: 0878ecff-5fdc-47d7-8c8f-edf4556f9746

+     :setup: Standalone Instance

+     :steps:

+         1. Create a use entry

+         2. Create DNA shared config entry container

+         3. Create DNA shared config entry

+         4. Add DNA plugin config entry

+         5. Enable DNA plugin

+         6. Restart the instance

+         7. Replace dnaType with invalid value

+     :expectedresults:

+         1. Successful

+         2. Successful

+         3. Successful

+         4. Successful

+         5. Successful

+         6. Successful

+         7. Unwilling to perform exception should be raised

+     """

+ 

+     inst = topology_st.standalone

+     plugin = DNAPlugin(inst)

+ 

+     log.info("Creating an entry...")

+     users = UserAccounts(inst, DEFAULT_SUFFIX)

+     users.create_test_user(uid=1)

+ 

+     log.info("Creating \"ou=ranges\"...")

+     ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)

+     ou_ranges = ous.create(properties={'ou': 'ranges'})

+     ou_people = ous.get("People")

+ 

+     log.info("Creating DNA shared config entry...")

+     shared_configs = DNAPluginSharedConfigs(inst, ou_ranges.dn)

+     shared_configs.create(properties={'dnaHostName': str(inst.host),

+                                       'dnaPortNum': str(inst.port),

+                                       'dnaRemainingValues': '9501'})

+ 

+     log.info("Add dna plugin config entry...")

+     configs = DNAPluginConfigs(inst, plugin.dn)

+     config = configs.create(properties={'cn': 'dna config',

+                                         'dnaType': 'description',

+                                         'dnaMaxValue': '10000',

+                                         'dnaMagicRegen': '0',

+                                         'dnaFilter': '(objectclass=top)',

+                                         'dnaScope': ou_people.dn,

+                                         'dnaNextValue': '500',

+                                         'dnaSharedCfgDN': ou_ranges.dn})

+ 

+     log.info("Enable the DNA plugin...")

+     plugin.enable()

+ 

+     log.info("Restarting the server...")

+     inst.restart()

+ 

+     log.info("Apply an invalid attribute to the DNA config(dnaType: foo)...")

+     with pytest.raises(ldap.UNWILLING_TO_PERFORM):

+         config.replace('dnaType', 'foo')

@@ -1,122 +0,0 @@ 

- # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016 Red Hat, Inc.

- # All rights reserved.

- #

- # License: GPL (version 3 or any later version).

- # See LICENSE for details.

- # --- END COPYRIGHT BLOCK ---

- #

- import logging

- import time

- 

- import ldap

- import pytest

- from lib389 import Entry

- from lib389._constants import *

- from lib389.topologies import topology_st

- 

- log = logging.getLogger(__name__)

- 

- 

- def test_ticket47937(topology_st):

-     """

-         Test that DNA plugin only accepts valid attributes for "dnaType"

-     """

- 

-     log.info("Creating \"ou=people\"...")

-     try:

-         topology_st.standalone.add_s(Entry(('ou=people,' + SUFFIX, {

-             'objectclass': 'top organizationalunit'.split(),

-             'ou': 'people'

-         })))

- 

-     except ldap.ALREADY_EXISTS:

-         pass

-     except ldap.LDAPError as e:

-         log.error('Failed to add ou=people org unit: error ' + e.args[0]['desc'])

-         assert False

- 

-     log.info("Creating \"ou=ranges\"...")

-     try:

-         topology_st.standalone.add_s(Entry(('ou=ranges,' + SUFFIX, {

-             'objectclass': 'top organizationalunit'.split(),

-             'ou': 'ranges'

-         })))

- 

-     except ldap.LDAPError as e:

-         log.error('Failed to add ou=ranges org unit: error ' + e.args[0]['desc'])

-         assert False

- 

-     log.info("Creating \"cn=entry\"...")

-     try:

-         topology_st.standalone.add_s(Entry(('cn=entry,ou=people,' + SUFFIX, {

-             'objectclass': 'top groupofuniquenames'.split(),

-             'cn': 'entry'

-         })))

- 

-     except ldap.LDAPError as e:

-         log.error('Failed to add test entry: error ' + e.args[0]['desc'])

-         assert False

- 

-     log.info("Creating DNA shared config entry...")

-     try:

-         topology_st.standalone.add_s(Entry(('dnaHostname=localhost.localdomain+dnaPortNum=389,ou=ranges,%s' % SUFFIX, {

-             'objectclass': 'top dnaSharedConfig'.split(),

-             'dnaHostname': 'localhost.localdomain',

-             'dnaPortNum': '389',

-             'dnaSecurePortNum': '636',

-             'dnaRemainingValues': '9501'

-         })))

- 

-     except ldap.LDAPError as e:

-         log.error('Failed to add shared config entry: error ' + e.args[0]['desc'])

-         assert False

- 

-     log.info("Add dna plugin config entry...")

-     try:

-         topology_st.standalone.add_s(

-             Entry(('cn=dna config,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config', {

-                 'objectclass': 'top dnaPluginConfig'.split(),

-                 'dnaType': 'description',

-                 'dnaMaxValue': '10000',

-                 'dnaMagicRegen': '0',

-                 'dnaFilter': '(objectclass=top)',

-                 'dnaScope': 'ou=people,%s' % SUFFIX,

-                 'dnaNextValue': '500',

-                 'dnaSharedCfgDN': 'ou=ranges,%s' % SUFFIX

-             })))

- 

-     except ldap.LDAPError as e:

-         log.error('Failed to add DNA config entry: error ' + e.args[0]['desc'])

-         assert False

- 

-     log.info("Enable the DNA plugin...")

-     try:

-         topology_st.standalone.plugins.enable(name=PLUGIN_DNA)

-     except e:

-         log.error("Failed to enable DNA Plugin: error " + e.args[0]['desc'])

-         assert False

- 

-     log.info("Restarting the server...")

-     topology_st.standalone.stop(timeout=120)

-     time.sleep(1)

-     topology_st.standalone.start(timeout=120)

-     time.sleep(3)

- 

-     log.info("Apply an invalid attribute to the DNA config(dnaType: foo)...")

- 

-     try:

-         topology_st.standalone.modify_s('cn=dna config,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config',

-                                         [(ldap.MOD_REPLACE, 'dnaType', b'foo')])

-     except ldap.LDAPError as e:

-         log.info('Operation failed as expected (error: %s)' % e.args[0]['desc'])

-     else:

-         log.error('Operation incorectly succeeded!  Test Failed!')

-         assert False

- 

- 

- if __name__ == '__main__':

-     # Run isolated

-     # -s for DEBUG mode

-     CURRENT_FILE = os.path.realpath(__file__)

-     pytest.main("-s %s" % CURRENT_FILE)

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="Account Policy Plugin"

                      pluginName="Account Policy"

-                     cmdName="accountpolicy"

+                     cmdName="account-policy"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="attribute uniqueness"

                      pluginName="Attribute Uniqueness"

-                     cmdName="attruniq"

+                     cmdName="attr-uniq"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="Linked Attributes"

                      pluginName="Linked Attributes"

-                     cmdName="linkedattr"

+                     cmdName="linked-attr"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="Managed Entries"

                      pluginName="Managed Entries"

-                     cmdName="managedentries"

+                     cmdName="managed-entries"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -369,7 +369,7 @@ 

      }

  

      editConfig() {

-         this.cmdOperation("edit");

+         this.cmdOperation("set");

      }

  

      handleCheckboxChange(e) {
@@ -473,7 +473,7 @@ 

              "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

              "plugin",

              "memberof",

-             "edit",

+             "set",

              "--scope",

              memberOfEntryScope || "delete",

              "--exclude",

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="Pass Through Authentication"

                      pluginName="Pass Through Authentication"

-                     cmdName="passthroughauth"

+                     cmdName="pass-through-auth"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="referential integrity postoperation"

                      pluginName="Referential Integrity"

-                     cmdName="referint"

+                     cmdName="referential-integrity"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="Retro Changelog Plugin"

                      pluginName="Retro Changelog"

-                     cmdName="retrochangelog"

+                     cmdName="retro-changelog"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -13,7 +13,7 @@ 

                      serverId={this.props.serverId}

                      cn="RootDN Access Control"

                      pluginName="RootDN Access Control"

-                     cmdName="rootdn"

+                     cmdName="root-dn"

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

@@ -196,7 +196,7 @@ 

              "-j",

              "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

              "plugin",

-             "edit",

+             "set",

              data.name,

              "--type",

              data.type || "delete",

@@ -705,7 +705,11 @@ 

          if self._must_attributes is not None:

              for attr in self._must_attributes:

                  if properties.get(attr, None) is None:

-                     raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr)

+                     # Put RDN to properties

+                     if attr == self._rdn_attribute and rdn is not None:

+                         properties[self._rdn_attribute] = ldap.dn.str2dn(rdn)[0][0][1]

+                     else:

+                         raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr)

  

          # Make sure the naming attribute is present

          if properties.get(self._rdn_attribute, None) is None and rdn is None:

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -23,19 +23,30 @@ 

      return attrs

  

  

- def generic_object_add(dsldap_object, log, args, arg_to_attr, props={}):

-     """Create an entry using DSLdapObject interface

+ def generic_object_add(dsldap_objects_class, inst, log, args, arg_to_attr, dn=None, basedn=None, props={}):

+     """Create an entry using DSLdapObjects interface

  

-     dsldap_object should be a single instance of DSLdapObject with a set dn

+     dsldap_objects should be a class inherited from the DSLdapObjects class

      """

  

      log = log.getChild('generic_object_add')

      # Gather the attributes

      attrs = _args_to_attrs(args, arg_to_attr)

-     # Update the parameters (which should have at least 'cn') with arg attributes

      props.update({attr: value for (attr, value) in attrs.items() if value != ""})

-     new_object = dsldap_object.create(properties=props)

+ 

+     # Get RDN attribute and Base DN from the DN if Base DN is not specified

+     if dn is not None and basedn is None:

+         dn_parts = ldap.dn.explode_dn(dn)

+ 

+         rdn = dn_parts[0]

+         basedn = ",".join(dn_parts[1:])

+     else:

+         raise ValueError('If Base DN is not specified - DN parameter should be')

+ 

+     new_object = dsldap_objects_class(inst, dn=dn)

+     new_object.create(rdn=rdn, basedn=basedn, properties=props)

      log.info("Successfully created the %s", new_object.dn)

+     return new_object

  

  

  def generic_object_edit(dsldap_object, log, args, arg_to_attr):

@@ -17,7 +17,6 @@ 

  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_conf.plugins import accountpolicy as cli_accountpolicy

  from lib389.cli_conf.plugins import attruniq as cli_attruniq
@@ -42,7 +41,8 @@ 

      'vendor': 'nsslapd-pluginVendor',

      'description': 'nsslapd-pluginDescription',

      'depends_on_type': 'nsslapd-plugin-depends-on-type',

-     'depends_on_named': 'nsslapd-plugin-depends-on-named'

+     'depends_on_named': 'nsslapd-plugin-depends-on-named',

+     'precedence': 'nsslapd-pluginPrecedence'

  }

  

  
@@ -111,16 +111,15 @@ 

      cli_managedentries.create_parser(subcommands)

      cli_passthroughauth.create_parser(subcommands)

      cli_retrochangelog.create_parser(subcommands)

-     cli_whoami.create_parser(subcommands)

  

      list_parser = subcommands.add_parser('list', help="List current configured (enabled and disabled) plugins")

      list_parser.set_defaults(func=plugin_list)

  

-     get_parser = subcommands.add_parser('get', help='Get the plugin data')

+     get_parser = subcommands.add_parser('show', help='Show the plugin data')

      get_parser.set_defaults(func=plugin_get)

      get_parser.add_argument('selector', nargs='?', help='The plugin to search for')

  

-     edit_parser = subcommands.add_parser('edit', help='Edit the plugin')

+     edit_parser = subcommands.add_parser('set', help='Edit the plugin')

      edit_parser.set_defaults(func=plugin_edit)

      edit_parser.add_argument('selector', nargs='?', help='The plugin to edit')

      edit_parser.add_argument('--type', help='The type of plugin.')
@@ -138,3 +137,4 @@ 

      edit_parser.add_argument('--depends-on-named',

                               help='The plug-in name matching one of the following values will be '

                                    'started by the server prior to this plug-in')

+     edit_parser.add_argument('--precedence', help='The priority it has in the execution order of plug-ins')

@@ -1,16 +1,118 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

- from lib389.plugins import AccountPolicyPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ import ldap

+ from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfigs, AccountPolicyConfig

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

+ 

+ arg_to_attr = {

+     'config_entry': 'nsslapd-pluginConfigArea'

+ }

+ 

+ arg_to_attr_config = {

+     'alt_state_attr': 'altstateattrname',

+     'always_record_login': 'alwaysRecordLogin',

+     'always_record_login_attr': 'alwaysRecordLoginAttr',

+     'limit_attr': 'limitattrname',

+     'spec_attr': 'specattrname',

+     'state_attr': 'stateattrname'

+ }

+ 

+ def accountpolicy_edit(inst, basedn, log, args):

+     log = log.getChild('accountpolicy_edit')

+     plugin = AccountPolicyPlugin(inst)

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def accountpolicy_add_config(inst, basedn, log, args):

+     log = log.getChild('accountpolicy_add_config')

+     targetdn = args.DN

+     config = generic_object_add(AccountPolicyConfig, inst, log, args, arg_to_attr_config, dn=targetdn)

+     plugin = AccountPolicyPlugin(inst)

+     plugin.replace('nsslapd_pluginConfigArea', config.dn)

+     log.info('Account Policy attribute nsslapd-pluginConfigArea (config_entry) '

+              'was set in the main plugin config')

+ 

+ 

+ def accountpolicy_edit_config(inst, basedn, log, args):

+     log = log.getChild('accountpolicy_edit_config')

+     targetdn = args.DN

+     config = AccountPolicyConfig(inst, targetdn)

+     generic_object_edit(config, log, args, arg_to_attr_config)

+ 

+ 

+ def accountpolicy_show_config(inst, basedn, log, args):

+     log = log.getChild('accountpolicy_show_config')

+     targetdn = args.DN

+     config = AccountPolicyConfig(inst, targetdn)

+ 

+     if not config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)

+     if args and args.json:

+         o_str = config.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(config.display())

+ 

+ 

+ def accountpolicy_del_config(inst, basedn, log, args):

+     log = log.getChild('accountpolicy_del_config')

+     targetdn = args.DN

+     config = AccountPolicyConfig(inst, targetdn)

+     config.delete()

+     log.info("Successfully deleted the %s", targetdn)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('--always-record-login', choices=['yes', 'no'],

+                         help='Sets that every entry records its last login time (alwaysRecordLogin)')

+     parser.add_argument('--alt-state-attr',

+                         help='Provides a backup attribute for the server to reference '

+                              'to evaluate the expiration time (altStateAttrName)')

+     parser.add_argument('--always-record-login-attr',

+                         help='Specifies the attribute to store the time of the last successful '

+                              'login in this attribute in the users directory entry (alwaysRecordLoginAttr)')

+     parser.add_argument('--limit-attr',

+                         help='Specifies the attribute within the policy to use '

+                              'for the account inactivation limit (limitAttrName)')

+     parser.add_argument('--spec-attr',

+                         help='Specifies the attribute to identify which entries '

+                              'are account policy configuration entries (specAttrName)')

+     parser.add_argument('--state-attr',

+                         help='Specifies the primary time attribute used to evaluate an account policy (stateAttrName)')

  

  

  def create_parser(subparsers):

-     accountpolicy_parser = subparsers.add_parser('accountpolicy', help='Manage and configure Account Policy plugin')

-     subcommands = accountpolicy_parser.add_subparsers(help='action')

+     accountpolicy = subparsers.add_parser('account-policy', help='Manage and configure Account Policy plugin')

+     subcommands = accountpolicy.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, AccountPolicyPlugin)

+ 

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=accountpolicy_edit)

+     edit.add_argument('--config-entry', help='The value to set as nsslapd-pluginConfigArea')

+ 

+     config = subcommands.add_parser('config-entry', help='Manage the config entry')

+     config_subcommands = config.add_subparsers(help='action')

+ 

+     add_config = config_subcommands.add_parser('add', help='Add the config entry')

+     add_config.set_defaults(func=accountpolicy_add_config)

+     add_config.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(add_config)

+ 

+     edit_config = config_subcommands.add_parser('set', help='Edit the config entry')

+     edit_config.set_defaults(func=accountpolicy_edit_config)

+     edit_config.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(edit_config)

+ 

+     show_config_parser = config_subcommands.add_parser('show', help='Display the config entry')

+     show_config_parser.set_defaults(func=accountpolicy_show_config)

+     show_config_parser.add_argument('DN', help='The config entry full DN')

+ 

+     del_config_parser = config_subcommands.add_parser('delete', help='Delete the config entry')

+     del_config_parser.set_defaults(func=accountpolicy_del_config)

+     del_config_parser.add_argument('DN', help='The config entry full DN')

@@ -1,16 +1,122 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

- from lib389.plugins import AttributeUniquenessPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ import json

+ import ldap

+ from lib389.plugins import AttributeUniquenessPlugin, AttributeUniquenessPlugins

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

+ from lib389._constants import DN_PLUGIN

+ 

+ arg_to_attr = {

+     'attr-name': 'uniqueness-attribute-name',

+     'subtree': 'uniqueness-subtrees',

+     'across-all-subtrees': 'uniqueness-across-all-subtrees',

+     'top-entry-oc': 'uniqueness-top-entry-oc',

+     'subtree-entries-oc': 'uniqueness-subtree-entries-oc'

+ }

+ 

+ 

+ def attruniq_list(inst, basedn, log, args):

+     log = log.getChild('attruniq_list')

+     plugins = AttributeUniquenessPlugins(inst)

+     result = []

+     result_json = []

+     for plugin in plugins.list():

+         if args.json:

+             result_json.append(plugin.get_all_attrs_json())

+         else:

+             result.append(plugin.rdn)

+     if args.json:

+         print(json.dumps({"type": "list", "items": result_json}))

+     else:

+         if len(result) > 0:

+             for i in result:

+                 print(i)

+         else:

+             print("No Attribute Uniqueness plugin instances")

+ 

+ 

+ def attruniq_add(inst, basedn, log, args):

+     log = log.getChild('attruniq_add')

+     props = {'cn': args.NAME}

+     generic_object_add(AttributeUniquenessPlugin, inst, log, args, arg_to_attr, basedn=DN_PLUGIN, props=props)

+ 

+ 

+ def attruniq_edit(inst, basedn, log, args):

+     log = log.getChild('attruniq_edit')

+     plugins = AttributeUniquenessPlugins(inst)

+     plugin = plugins.get(args.NAME)

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def attruniq_show(inst, basedn, log, args):

+     log = log.getChild('attruniq_show')

+     plugins = AttributeUniquenessPlugins(inst)

+     plugin = plugins.get(args.NAME)

+ 

+     if not plugin.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

+     if args and args.json:

+         o_str = plugin.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(plugin.display())

+ 

+ 

+ def attruniq_del(inst, basedn, log, args):

+     log = log.getChild('attruniq_del')

+     plugins = AttributeUniquenessPlugins(inst)

+     plugin = plugins.get(args.NAME)

+     plugin.delete()

+     log.info("Successfully deleted the %s", plugin.dn)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('NAME', help='Sets the name of the plug-in configuration record. (cn) You can use any string, '

+                                      'but "attribute_name Attribute Uniqueness" is recommended.')

+     parser.add_argument('--attr-name', nargs='+',

+                         help='Sets the name of the attribute whose values must be unique. '

+                              'This attribute is multi-valued. (uniqueness-attribute-name)')

+     parser.add_argument('--subtree', nargs='+',

+                         help='Sets the DN under which the plug-in checks for uniqueness of '

+                              'the attributes value. This attribute is multi-valued (uniqueness-subtrees)')

+     parser.add_argument('--across-all-subtrees', choices=['on', 'off'],

+                         help='If enabled (on), the plug-in checks that the attribute is unique across all subtrees '

+                              'set. If you set the attribute to off, uniqueness is only enforced within the subtree '

+                              'of the updated entry (uniqueness-across-all-subtrees)')

+     parser.add_argument('--top-entry-oc',

+                         help='Verifies that the value of the attribute set in uniqueness-attribute-name '

+                              'is unique in this subtree (uniqueness-top-entry-oc)')

+     parser.add_argument('--subtree-entries-oc',

+                         help='Verifies if an attribute is unique, if the entry contains the object class '

+                              'set in this parameter (uniqueness-subtree-entries-oc)')

  

  

  def create_parser(subparsers):

-     attruniq_parser = subparsers.add_parser('attruniq', help='Manage and configure Attribute Uniqueness plugin')

-     subcommands = attruniq_parser.add_subparsers(help='action')

+     attruniq = subparsers.add_parser('attr-uniq', help='Manage and configure Attribute Uniqueness plugin')

+     subcommands = attruniq.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, AttributeUniquenessPlugin)

+ 

+     list = subcommands.add_parser('list', help='List available plugin configs')

+     list.set_defaults(func=attruniq_list)

+ 

+     add = subcommands.add_parser('add', help='Add the config entry')

+     add.set_defaults(func=attruniq_add)

+     _add_parser_args(add)

+ 

+     edit = subcommands.add_parser('set', help='Edit the config entry')

+     edit.set_defaults(func=attruniq_edit)

+     _add_parser_args(edit)

+ 

+     show = subcommands.add_parser('show', help='Display the config entry')

+     show.add_argument('NAME', help='The name of the plug-in configuration record')

+     show.set_defaults(func=attruniq_show)

+ 

+     delete = subcommands.add_parser('delete', help='Delete the config entry')

+     delete.add_argument('NAME', help='Sets the name of the plug-in configuration record')

+     delete.set_defaults(func=attruniq_del)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -8,165 +8,226 @@ 

  

  import ldap

  import json

- from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions

- from lib389.cli_conf import add_generic_plugin_parsers

+ from lib389.plugins import (AutoMembershipPlugin, AutoMembershipDefinition, AutoMembershipDefinitions,

+                             AutoMembershipRegexRule, AutoMembershipRegexRules)

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

  

  

- def list_definition(inst, basedn, log, args):

-     """List automember definition if instance name

-     is given else show all automember definitions.

+ arg_to_attr_definition = {

+     'default-group': 'autoMemberDefaultGroup',

+     'filter': 'autoMemberFilter',

+     'grouping-attr': 'autoMemberGroupingAttr',

+     'scope': 'autoMemberScope'

+ }

  

-     :param name: An instance

-     :type name: lib389.DirSrv

-     """

-     

-     automembers = AutoMembershipDefinitions(inst)

- 

-     if args.name is not None:

-         if args.json:

-             print(automembers.get_all_attrs_json(args.name))

-         else:

-             automember = automembers.get(args.name)

-             log.info(automember.display())

-     else:    

-         all_definitions = automembers.list()

-         if args.json:

-             result = {'type': 'list', 'items': []}

-         if len(all_definitions) > 0:

-             for definition in all_definitions:

-                 if args.json:

-                     result['items'].append(definition)

-                 else:

-                     log.info(definition.display())

-         else:

-             log.info("No automember definitions were found")

- 

-         if args.json:

-             print(json.dumps(result))

- 

- 

- def create_definition(inst, basedn, log, args):

-     """

-         Create automember definition.

- 

-         :param name: An instance

-         :type name: lib389.DirSrv

-         :param groupattr: autoMemberGroupingAttr value

-         :type groupattr: str

-         :param defaultgroup: autoMemberDefaultGroup value

-         :type defaultgroup: str

-         :param scope: autoMemberScope value

-         :type scope: str

-         :param filter: autoMemberFilter value

-         :type filter: str

- 

-     """

-     automember_prop = {

-         'cn': args.name,

-         'autoMemberScope': args.scope,

-         'autoMemberFilter': args.filter,

-         'autoMemberDefaultGroup': args.defaultgroup,

-         'autoMemberGroupingAttr': args.groupattr,

-     }

- 

-     plugin = AutoMembershipPlugin(inst)

-     plugin.enable()

- 

-     automembers = AutoMembershipDefinitions(inst)

-     

-     try:

-         automember = automembers.create(properties=automember_prop)

-         log.info("Automember definition created successfully!")

-     except Exception as e:

-         log.info("Failed to create Automember definition: {}".format(str(e)))

-         raise e

- 

- 

- def edit_definition(inst, basedn, log, args):

-     """

-         Edit automember definition

-         

-         :param name: An instance

-         :type name: lib389.DirSrv

-         :param groupattr: autoMemberGroupingAttr value

-         :type groupattr: str

-         :param defaultgroup: autoMemberDefaultGroup value

-         :type defaultgroup: str

-         :param scope: autoMemberScope value

-         :type scope: str

-         :param filter: autoMemberFilter value

-         :type filter: str

- 

-     """

-     automembers = AutoMembershipDefinitions(inst)

-     automember = automembers.get(args.name)

+ arg_to_attr_regex = {

+     'exclusive': 'autoMemberExclusiveRegex',

+     'inclusive': 'autoMemberInclusiveRegex',

+     'target-group': 'autoMemberTargetGroup'

+ }

  

-     if args.scope is not None:

-         automember.replace("automemberscope", args.scope)

-     if args.filter is not None:

-         automember.replace("automemberfilter", args.filter)

-     if args.defaultgroup is not None:

-         automember.replace("automemberdefaultgroup", args.defaultgroup)

-     if args.groupattr is not None:

-         automember.replace("automembergroupingattr", args.groupattr)

-     log.info("Definition updated successfully.")

  

- 

- def remove_definition(inst, basedn, log, args):

-     """

-         Remove automember definition for the given

-         instance.

- 

-         :param name: An instance

-         :type name: lib389.DirSrv

- 

-     """

+ def definition_list(inst, basedn, log, args):

      automembers = AutoMembershipDefinitions(inst)

-     automember = automembers.get(args.name)

- 

-     automember.delete()

-     log.info("Definition deleted successfully.")

+     all_definitions = automembers.list()

+     if args.json:

+         result = {'type': 'list', 'items': []}

+     if len(all_definitions) > 0:

+         for definition in all_definitions:

+             if args.json:

+                 result['items'].append(definition)

+             else:

+                 log.info(definition.rdn)

+     else:

+         log.info("No automember definitions were found")

+ 

+     if args.json:

+         print(json.dumps(result))

+ 

+ 

+ def definition_add(inst, basedn, log, args):

+     log = log.getChild('definition_add')

+     plugin = AutoMembershipPlugin(inst)

+     props = {'cn': args.DEF_NAME}

+     generic_object_add(AutoMembershipDefinition, inst, log, args, arg_to_attr_definition, basedn=plugin.dn, props=props)

+ 

+ 

+ def definition_edit(inst, basedn, log, args):

+     log = log.getChild('definition_edit')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     generic_object_edit(definition, log, args, arg_to_attr_definition)

+ 

+ 

+ def definition_show(inst, basedn, log, args):

+     log = log.getChild('definition_show')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+ 

+     if not definition.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

+     if args and args.json:

+         o_str = definition.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(definition.display())

+ 

+ 

+ def definition_del(inst, basedn, log, args):

+     log = log.getChild('definition_del')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     definition.delete()

+     log.info("Successfully deleted the %s definition", args.name)

+ 

+ 

+ def regex_list(inst, basedn, log, args):

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     regexes = AutoMembershipRegexRules(inst, definition.dn)

+     all_regexes = regexes.list()

+     if args.json:

+         result = {'type': 'list', 'items': []}

+     if len(all_regexes) > 0:

+         for regex in all_regexes:

+             if args.json:

+                 result['items'].append(regex)

+             else:

+                 log.info(regex.rdn)

+     else:

+         log.info("No automember regexes were found")

+ 

+     if args.json:

+         print(json.dumps(result))

+ 

+ 

+ def regex_add(inst, basedn, log, args):

+     log = log.getChild('regex_add')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     props = {'cn': args.REGEX_NAME}

+     generic_object_add(AutoMembershipRegexRule, inst, log, args, arg_to_attr_regex, basedn=definition.dn, props=props)

+ 

+ 

+ def regex_edit(inst, basedn, log, args):

+     log = log.getChild('regex_edit')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     regexes = AutoMembershipRegexRules(inst, definition.dn)

+     regex = regexes.get(args.REGEX_NAME)

+     generic_object_edit(regex, log, args, arg_to_attr_regex)

+ 

+ 

+ def regex_show(inst, basedn, log, args):

+     log = log.getChild('regex_show')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     regexes = AutoMembershipRegexRules(inst, definition.dn)

+     regex = regexes.get(args.REGEX_NAME)

+ 

+     if not regex.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

+     if args and args.json:

+         o_str = regex.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(regex.display())

+ 

+ 

+ def regex_del(inst, basedn, log, args):

+     log = log.getChild('regex_del')

+     definitions = AutoMembershipDefinitions(inst)

+     definition = definitions.get(args.DEF_NAME)

+     regexes = AutoMembershipRegexRules(inst, definition.dn)

+     regex = regexes.get(args.REGEX_NAME)

+     regex.delete()

+     log.info("Successfully deleted the %s regex", regex.dn)

+ 

+ 

+ def fixup(inst, basedn, log, args):

+     plugin = AutoMembershipPlugin(inst)

+     log.info('Attempting to add task entry... This will fail if Automembership plug-in is not enabled.')

+     if not plugin.status():

+         log.error("'%s' is disabled. Rebuild membership task can't be executed" % plugin.rdn)

+     fixup_task = plugin.fixup(args.DN, args.filter)

+     fixup_task.wait()

+     exitcode = fixup_task.get_exit_code()

+     if exitcode != 0:

+         log.error('Rebuild membership task for %s has failed. Please, check logs')

+     else:

+         log.info('Successfully added task entry')

+ 

+ 

+ def _add_parser_args_definition(parser):

+     parser.add_argument('--grouping-attr',

+                         help='Specifies the name of the member attribute in the group entry and '

+                              'the attribute in the object entry that supplies the member attribute value, '

+                              'in the format group_member_attr:entry_attr (autoMemberGroupingAttr)')

+     parser.add_argument('--default-group', required=True,

+                         help='Sets default or fallback group to add the entry to as a member '

+                              'member attribute in group entry (autoMemberDefaultGroup)')

+     parser.add_argument('--scope', required=True,

+                         help='Sets the subtree DN to search for entries (autoMemberScope)')

+     parser.add_argument('--filter',

+                         help='Sets a standard LDAP search filter to use to search for '

+                              'matching entries (autoMemberFilter)')

+ 

+ 

+ def _add_parser_args_regex(parser):

+     parser.add_argument("--exclusive",

+                         help='Sets a single regular expression to use to identify '

+                              'entries to exclude (autoMemberExclusiveRegex)')

+     parser.add_argument('--inclusive', required=True,

+                         help='Sets a single regular expression to use to identify '

+                              'entries to include (autoMemberInclusiveRegex)')

+     parser.add_argument('--target-group', required=True,

+                         help='Sets which group to add the entry to as a member, if it meets '

+                              'the regular expression conditions (autoMemberTargetGroup)')

  

  

  def create_parser(subparsers):

-     automember_parser = subparsers.add_parser('automember', help="Manage and configure automember plugin")

- 

-     subcommands = automember_parser.add_subparsers(help='action')

- 

+     automember = subparsers.add_parser('automember', help="Manage and configure Automembership plugin")

+     subcommands = automember.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, AutoMembershipPlugin)

  

-     create_parser = subcommands.add_parser('create', help='Create automember definition.')

-     create_parser.set_defaults(func=create_definition)

-     

-     create_parser.add_argument("name", help='Set cn for group entry.')

- 

-     create_parser.add_argument("--groupattr", help='Set member attribute in group entry.', default='member:dn')

- 

-     create_parser.add_argument('--defaultgroup', required=True, help='Set default group to add member to.')

- 

-     create_parser.add_argument('--scope', required=True, help='Set automember scope.')

- 

-     create_parser.add_argument('--filter', help='Set automember filter.', default= '(objectClass=*)')

- 

-     show_parser = subcommands.add_parser('list', help='List automember definition.')

-     show_parser.set_defaults(func=list_definition)

- 

-     show_parser.add_argument("--name", help='Set cn for group entry. If not specified show all automember definitions.')

- 

-     edit_parser = subcommands.add_parser('edit', help='Edit automember definition.')

-     edit_parser.set_defaults(func=edit_definition)

- 

-     edit_parser.add_argument("name", help='Set cn for group entry.')

- 

-     edit_parser.add_argument("--groupattr", help='Set member attribute in group entry.')

- 

-     edit_parser.add_argument('--defaultgroup', help='Set default group to add member to.')

- 

-     edit_parser.add_argument('--scope', help='Set automember scope.')

- 

-     edit_parser.add_argument('--filter', help='Set automember filter.')

- 

-     remove_parser = subcommands.add_parser('remove', help='Remove automember definition.')

-     remove_parser.set_defaults(func=remove_definition)

+     list = subcommands.add_parser('list', help='List Automembership definitions or regex rules.')

+     subcommands_list = list.add_subparsers(help='action')

+     list_definitions = subcommands_list.add_parser('definitions', help='List Automembership definitions.')

+     list_definitions.set_defaults(func=definition_list)

+     list_regexes = subcommands_list.add_parser('regexes', help='List Automembership regex rules.')

+     list_regexes.add_argument('DEF-NAME', help='The definition entry CN.')

+     list_regexes.set_defaults(func=regex_list)

+ 

+     definition = subcommands.add_parser('definition', help='Manage Automembership definition.')

+     definition.add_argument('DEF-NAME', help='The definition entry CN.')

+     subcommands_definition = definition.add_subparsers(help='action')

+ 

+     add_def = subcommands_definition.add_parser('add', help='Create Automembership definition.')

+     add_def.set_defaults(func=definition_add)

+     _add_parser_args_definition(add_def)

+     edit_def = subcommands_definition.add_parser('set', help='Edit Automembership definition.')

+     edit_def.set_defaults(func=definition_edit)

+     _add_parser_args_definition(edit_def)

+     delete_def = subcommands_definition.add_parser('delete', help='Remove Automembership definition.')

+     delete_def.set_defaults(func=definition_del)

+ 

+     regex = subcommands_definition.add_parser('regex', help='Manage Automembership regex rules.')

+     regex.add_argument('REGEX-NAME', help='The regex entry CN.')

+     subcommands_regex = regex.add_subparsers(help='action')

+ 

+     add_regex = subcommands_regex.add_parser('add', help='Create Automembership regex.')

+     add_regex.set_defaults(func=regex_add)

+     _add_parser_args_definition(add_regex)

+     edit_regex = subcommands_regex.add_parser('set', help='Edit Automembership regex.')

+     edit_regex.set_defaults(func=regex_edit)

+     _add_parser_args_definition(edit_regex)

+     delete_regex = subcommands_regex.add_parser('delete', help='Remove Automembership regex.')

+     delete_regex.set_defaults(func=regex_del)

+ 

+     fixup = subcommands.add_parser('fixup', help='Run a rebuild membership task.')

+     fixup.set_defaults(func=fixup)

+     fixup.add_argument('DN', help="Base DN that contains entries to fix up")

+     fixup.add_argument('-f', '--filter', required=True, help='LDAP filter for entries to fix up.')

+     fixup.add_argument('-s', '--scope', required=True, choices=['sub', 'base', 'one'], type=str.lower,

+                        help='LDAP search scope for entries to fix up')

  

-     remove_parser.add_argument("name", help='Set cn for group entry.')

@@ -1,16 +1,246 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

- from lib389.plugins import DNAPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ import json

+ import ldap

+ from lib389.plugins import DNAPlugin, DNAPluginConfig, DNAPluginConfigs, DNAPluginSharedConfig, DNAPluginSharedConfigs

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add, _args_to_attrs

+ 

+ arg_to_attr = {

+     'type': 'dnaType',

+     'prefix': 'dnaPrefix',

+     'next_value': 'dnaNextValue',

+     'max_value': 'dnaMaxValue',

+     'interval': 'dnaInterval',

+     'magic_regen': 'dnaMagicRegen',

+     'filter': 'dnaFilter',

+     'scope': 'dnaScope',

+     'remote_bind_dn': 'dnaRemoteBindDN',

+     'remote_bind_cred': 'dnaRemoteBindCred',

+     'shared_config_entry': 'dnaSharedCfgDN',

+     'threshold': 'dnaThreshold',

+     'next_range': 'dnaNextRange',

+     'range_request_timeout': 'dnaRangeRequestTimeout'

+ }

+ 

+ arg_to_attr_config = {

+     'hostname': 'dnaHostname',

+     'port': 'dnaPortNum',

+     'secure_port': 'dnaSecurePortNum',

+     'remaining_values': 'dnaRemainingValues',

+     'remote_bind_method': 'dnaRemoteBindMethod',

+     'remote_conn_protocol': 'dnaRemoteConnProtocol'

+ }

+ 

+ 

+ def dna_list(inst, basedn, log, args):

+     log = log.getChild('dna_list')

+     configs = DNAPluginConfigs(inst)

+     config_list = configs.list()

+     if args.json:

+         result = {'type': 'list', 'items': []}

+     if len(config_list) > 0:

+         for config in config_list:

+             if args.json:

+                 result['items'].append(config)

+             else:

+                 log.info(config.rdn)

+     else:

+         log.info("No DNA configurations were found")

+ 

+     if args.json:

+         print(json.dumps(result))

+ 

+ 

+ def dna_add(inst, basedn, log, args):

+     log = log.getChild('dna_add')

+     plugin = DNAPlugin(inst)

+     props = {'cn': args.NAME}

+     generic_object_add(DNAPluginConfig, inst, log, args, arg_to_attr, basedn=plugin.dn, props=props)

+ 

+ 

+ def dna_edit(inst, basedn, log, args):

+     log = log.getChild('dna_edit')

+     configs = DNAPluginConfigs(inst)

+     config = configs.get(args.NAME)

+     generic_object_edit(config, log, args, arg_to_attr)

+ 

+ 

+ def dna_show(inst, basedn, log, args):

+     log = log.getChild('dna_show')

+     configs = DNAPluginConfigs(inst)

+     config = configs.get(args.NAME)

+ 

+     if not config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.NAME)

+     if args and args.json:

+         o_str = config.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(config.display())

+ 

+ 

+ def dna_del(inst, basedn, log, args):

+     log = log.getChild('dna_del')

+     configs = DNAPluginConfigs(inst)

+     config = configs.get(args.NAME)

+     config.delete()

+     log.info("Successfully deleted the %s", config.dn)

+ 

+ 

+ def dna_config_list(inst, basedn, log, args):

+     log = log.getChild('dna_list')

+     configs = DNAPluginSharedConfigs(inst, args.BASEDN)

+     config_list = configs.list()

+     if args.json:

+         result = {'type': 'list', 'items': []}

+     if len(config_list) > 0:

+         for config in config_list:

+             if args.json:

+                 result['items'].append(config.get_all_attrs_json())

+             else:

+                 log.info(config.dn)

+     else:

+         log.info("No DNA shared configurations were found")

+ 

+     if args.json:

+         print(json.dumps(result))

+ 

+ 

+ def dna_config_add(inst, basedn, log, args):

+     log = log.getChild('dna_config_add')

+     targetdn = args.BASEDN

+ 

+     shared_configs = DNAPluginSharedConfigs(inst, targetdn)

+     attrs = _args_to_attrs(args, arg_to_attr_config)

+     props = {attr: value for (attr, value) in attrs.items() if value != ""}

+ 

+     shared_config = shared_configs.create(properties=props)

+     log.info("Successfully created the %s" % shared_config.dn)

+ 

+     configs = DNAPluginConfigs(inst)

+     config = configs.get(args.NAME)

+     config.replace('dnaSharedCfgDN', config.dn)

+     log.info('DNA attribute dnaSharedCfgDN (shared-config-entry) '

+              'was set in the %s plugin config' % config.rdn)

+ 

+ 

+ def dna_config_edit(inst, basedn, log, args):

+     log = log.getChild('dna_config_edit')

+     targetdn = args.DN

+     shared_config = DNAPluginSharedConfig(inst, targetdn)

+     generic_object_edit(shared_config, log, args, arg_to_attr_config)

+ 

+ 

+ def dna_config_show(inst, basedn, log, args):

+     log = log.getChild('dna_config_show')

+     targetdn = args.DN

+     shared_config = DNAPluginSharedConfig(inst, targetdn)

+ 

+     if not shared_config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)

+     if args and args.json:

+         o_str = shared_config.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(shared_config.display())

+ 

+ 

+ def dna_config_del(inst, basedn, log, args):

+     log = log.getChild('dna_config_del')

+     targetdn = args.DN

+     shared_config = DNAPluginSharedConfig(inst, targetdn)

+     shared_config.delete()

+     log.info("Successfully deleted the %s", targetdn)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('--type', help='Sets which attributes have unique numbers being generated for them (dnaType)')

+     parser.add_argument('--prefix', help='Defines a prefix that can be prepended to the generated '

+                                          'number values for the attribute (dnaPrefix)')

+     parser.add_argument('--next-value', help='Gives the next available number which can be assigned (dnaNextValue)')

+     parser.add_argument('--max-value', help='Sets the maximum value that can be assigned for the range (dnaMaxValue)')

+     parser.add_argument('--interval', help='Sets an interval to use to increment through numbers in a range (dnaInterval)')

+     parser.add_argument('--magic-regen', help='Sets a user-defined value that instructs the plug-in '

+                                               'to assign a new value for the entry (dnaMagicRegen)')

+     parser.add_argument('--filter', help='Sets an LDAP filter to use to search for and identify the entries '

+                                          'to which to apply the distributed numeric assignment range (dnaFilter)')

+     parser.add_argument('--scope', help='Sets the base DN to search for entries to which '

+                                         'to apply the distributed numeric assignment (dnaScope)')

+     parser.add_argument('--remote-bind-dn', help='Specifies the Replication Manager DN (dnaRemoteBindDN)')

+     parser.add_argument('--remote-bind-cred', help='Specifies the Replication Manager\'s password (dnaRemoteBindCred)')

+     parser.add_argument('--shared-config-entry', help='Defines a shared identity that the servers can use '

+                                                       'to transfer ranges to one another (dnaSharedCfgDN)')

+     parser.add_argument('--threshold', help='Sets a threshold of remaining available numbers in the range. When the '

+                                             'server hits the threshold, it sends a request for a new range (dnaThreshold)')

+     parser.add_argument('--next-range',

+                         help='Defines the next range to use when the current range is exhausted (dnaNextRange)')

+     parser.add_argument('--range-request-timeout',

+                         help='sets a timeout period, in seconds, for range requests so that the server '

+                              'does not stall waiting on a new range from one server and '

+                              'can request a range from a new server (dnaRangeRequestTimeout)')

+ 

+ 

+ def _add_parser_args_config(parser):

+     parser.add_argument('--hostname',

+                         help='Identifies the host name of a server in a shared range, as part of the DNA '

+                              'range configuration for that specific host in multi-master replication (dnaHostname)')

+     parser.add_argument('--port', help='Gives the standard port number to use to connect to '

+                                        'the host identified in dnaHostname (dnaPortNum)')

+     parser.add_argument('--secure-port', help='Gives the secure (TLS) port number to use to connect '

+                                               'to the host identified in dnaHostname (dnaSecurePortNum)')

+     parser.add_argument('--remote-bind-method', help='Specifies the remote bind method (dnaRemoteBindMethod)')

+     parser.add_argument('--remote-conn-protocol', help='Specifies the remote connection protocol (dnaRemoteConnProtocol)')

+     parser.add_argument('--remaining-values', help='Contains the number of values that are remaining and '

+                                                    'available to a server to assign to entries (dnaRemainingValues)')

  

  

  def create_parser(subparsers):

-     dna_parser = subparsers.add_parser('dna', help='Manage and configure DNA plugin')

-     subcommands = dna_parser.add_subparsers(help='action')

+     dna = subparsers.add_parser('dna', help='Manage and configure DNA plugin')

+     subcommands = dna.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, DNAPlugin)

+ 

+     list = subcommands.add_parser('list', help='List available plugin configs')

+     subcommands_list = list.add_subparsers(help='action')

+     list_configs = subcommands_list.add_parser('configs', help='List main DNA plugin config entries')

+     list_configs.set_defaults(func=dna_list)

+     list_shared_configs = subcommands_list.add_parser('shared-configs', help='List DNA plugin shared config entries')

+     list_shared_configs.add_argument('BASEDN', help='The search DN')

+     list_shared_configs.set_defaults(func=dna_config_list)

+ 

+     config = subcommands.add_parser('config', help='Manage plugin configs')

+     config.add_argument('NAME', help='The DNA configuration name')

+     config_subcommands = config.add_subparsers(help='action')

+     add = config_subcommands.add_parser('add', help='Add the config entry')

+     add.set_defaults(func=dna_add)

+     _add_parser_args(add)

+     edit = config_subcommands.add_parser('set', help='Edit the config entry')

+     edit.set_defaults(func=dna_edit)

+     _add_parser_args(edit)

+     show = config_subcommands.add_parser('show', help='Display the config entry')

+     show.set_defaults(func=dna_show)

+     delete = config_subcommands.add_parser('delete', help='Delete the config entry')

+     delete.set_defaults(func=dna_del)

+     shared_config = config_subcommands.add_parser('shared-config-entry', help='Manage the shared config entry')

+     shared_config_subcommands = shared_config.add_subparsers(help='action')

+ 

+     add_config = shared_config_subcommands.add_parser('add', help='Add the shared config entry')

+     add_config.add_argument('BASEDN', help='The shared config entry BASE DN. The new DN will be constructed with '

+                                            'dnaHostname and dnaPortNum')

+     add_config.set_defaults(func=dna_config_add)

+     _add_parser_args_config(add_config)

+     edit_config = shared_config_subcommands.add_parser('edit', help='Edit the shared config entry')

+     edit_config.add_argument('DN', help='The shared config entry DN')

+     edit_config.set_defaults(func=dna_config_edit)

+     _add_parser_args_config(edit_config)

+     show_config_parser = shared_config_subcommands.add_parser('show', help='Display the shared config entry')

+     show_config_parser.add_argument('DN', help='The shared config entry DN')

+     show_config_parser.set_defaults(func=dna_config_show)

+     del_config_parser = shared_config_subcommands.add_parser('delete', help='Delete the shared config entry')

+     del_config_parser.add_argument('DN', help='The shared config entry DN')

+     del_config_parser.set_defaults(func=dna_config_del)

@@ -1,4 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2019 Red Hat, Inc.

  # Copyright (C) 2019 William Brown <william@blackhats.net.au>

  # All rights reserved.

  #
@@ -6,8 +7,72 @@ 

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

- from lib389.plugins import LinkedAttributesPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ import json

+ import ldap

+ from lib389.plugins import LinkedAttributesPlugin, LinkedAttributesConfig, LinkedAttributesConfigs

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

+ 

+ arg_to_attr = {

+     'link_type': 'linkType',

+     'managed_type': 'managedType',

+     'link_scope': 'linkScope',

+ }

+ 

+ 

+ def linkedattr_list(inst, basedn, log, args):

+     log = log.getChild('linkedattr_list')

+     configs = LinkedAttributesConfigs(inst)

+     result = []

+     result_json = []

+     for config in configs.list():

+         if args.json:

+             result_json.append(config.get_all_attrs_json())

+         else:

+             result.append(config.rdn)

+     if args.json:

+         print(json.dumps({"type": "list", "items": result_json}))

+     else:

+         if len(result) > 0:

+             for i in result:

+                 print(i)

+         else:

+             print("No Linked Attributes plugin instances")

+ 

+ 

+ def linkedattr_add(inst, basedn, log, args):

+     log = log.getChild('linkedattr_add')

+     plugin = LinkedAttributesPlugin(inst)

+     props = {'cn': args.NAME}

+     generic_object_add(LinkedAttributesConfig, inst, log, args, arg_to_attr, basedn=plugin.dn, props=props)

+ 

+ 

+ def linkedattr_edit(inst, basedn, log, args):

+     log = log.getChild('linkedattr_edit')

+     configs = LinkedAttributesConfigs(inst)

+     config = configs.get(args.NAME)

+     generic_object_edit(config, log, args, arg_to_attr)

+ 

+ 

+ def linkedattr_show(inst, basedn, log, args):

+     log = log.getChild('linkedattr_show')

+     configs = LinkedAttributesConfigs(inst)

+     config = configs.get(args.NAME)

+ 

+     if not config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

+     if args and args.json:

+         o_str = config.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(config.display())

+ 

+ 

+ def linkedattr_del(inst, basedn, log, args):

+     log = log.getChild('linkedattr_del')

+     configs = LinkedAttributesConfigs(inst)

+     config = configs.get(args.NAME)

+     config.delete()

+     log.info("Successfully deleted the %s", config.dn)

  

  

  def fixup(inst, basedn, log, args):
@@ -24,8 +89,17 @@ 

          log.info('Successfully added fixup task')

  

  

+ def _add_parser_args(parser):

+     parser.add_argument('--link-type',

+                         help='Sets the attribute that is managed manually by administrators (linkType)')

+     parser.add_argument('--managed-type',

+                         help='Sets the attribute that is created dynamically by the plugin (managedType)')

+     parser.add_argument('--link-scope',

+                         help='Sets the scope that restricts the plugin to a specific part of the directory tree (linkScope)')

+ 

+ 

  def create_parser(subparsers):

-     linkedattr_parser = subparsers.add_parser('linkedattr', help='Manage and configure Linked Attributes plugin')

+     linkedattr_parser = subparsers.add_parser('linked-attr', help='Manage and configure Linked Attributes plugin')

      subcommands = linkedattr_parser.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, LinkedAttributesPlugin)

  
@@ -33,3 +107,20 @@ 

      fixup_parser.add_argument('basedn', help="basedn that contains entries to fix up")

      fixup_parser.add_argument('-f', '--filter', help='Filter for entries to fix up linked attributes.')

      fixup_parser.set_defaults(func=fixup)

+ 

+     list = subcommands.add_parser('list', help='List available plugin configs')

+     list.set_defaults(func=linkedattr_list)

+ 

+     config = subcommands.add_parser('config', help='Manage plugin configs')

+     config.add_argument('NAME', help='The Linked Attributes configuration name')

+     config_subcommands = config.add_subparsers(help='action')

+     add = config_subcommands.add_parser('add', help='Add the config entry')

+     add.set_defaults(func=linkedattr_add)

+     _add_parser_args(add)

+     edit = config_subcommands.add_parser('set', help='Edit the config entry')

+     edit.set_defaults(func=linkedattr_edit)

+     _add_parser_args(edit)

+     show = config_subcommands.add_parser('show', help='Display the config entry')

+     show.set_defaults(func=linkedattr_show)

+     delete = config_subcommands.add_parser('delete', help='Delete the config entry')

+     delete.set_defaults(func=linkedattr_del)

@@ -1,16 +1,231 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

- from lib389.plugins import ManagedEntriesPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ import ldap

+ import json

+ from lib389.plugins import ManagedEntriesPlugin, MEPConfig, MEPConfigs, MEPTemplate, MEPTemplates

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

+ 

+ arg_to_attr = {

+     'config_area': 'nsslapd-pluginConfigArea'

+ }

+ 

+ arg_to_attr_config = {

+     'scope': 'originScope',

+     'filter': 'originFilter',

+     'managed_base': 'managedBase',

+     'managed_template': 'managedTemplate'

+ }

+ 

+ arg_to_attr_template = {

+     'rdn_attr': 'mepRDNAttr',

+     'static_attr': 'mepStaticAttr',

+     'mapped_attr': 'mepMappedAttr'

+ }

+ 

+ 

+ def mep_edit(inst, basedn, log, args):

+     log = log.getChild('mep_edit')

+     plugin = ManagedEntriesPlugin(inst)

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def mep_config_list(inst, basedn, log, args):

+     log = log.getChild('mep_config_list')

+     plugin = ManagedEntriesPlugin(inst)

+     config_area = plugin.get_attr_val_utf8_l('nsslapd-pluginConfigArea')

+     configs = MEPConfigs(inst, config_area)

+     result = []

+     result_json = []

+     for config in configs.list():

+         if args.json:

+             result_json.append(config.get_all_attrs_json())

+         else:

+             result.append(config.rdn)

+     if args.json:

+         print(json.dumps({"type": "list", "items": result_json}))

+     else:

+         if len(result) > 0:

+             for i in result:

+                 print(i)

+         else:

+             print("No Linked Attributes plugin instances")

+ 

+ 

+ def mep_config_add(inst, basedn, log, args):

+     log = log.getChild('mep_config_add')

+     plugin = ManagedEntriesPlugin(inst)

+     config_area = plugin.get_attr_val_utf8_l('nsslapd-pluginConfigArea')

+     if config_area is None:

+         config_area = plugin.dn

+     props = {'cn': args.NAME}

+     generic_object_add(MEPConfig, inst, log, args, arg_to_attr_config, basedn=config_area, props=props)

+ 

+ 

+ def mep_config_edit(inst, basedn, log, args):

+     log = log.getChild('mep_config_edit')

+     plugin = ManagedEntriesPlugin(inst)

+     config_area = plugin.get_attr_val_utf8_l('nsslapd-pluginConfigArea')

+     configs = MEPConfigs(inst, config_area)

+     config = configs.get(args.NAME)

+     generic_object_edit(config, log, args, arg_to_attr_config)

+ 

+ 

+ def mep_config_show(inst, basedn, log, args):

+     log = log.getChild('mep_config_show')

+     plugin = ManagedEntriesPlugin(inst)

+     config_area = plugin.get_attr_val_utf8_l('nsslapd-pluginConfigArea')

+     configs = MEPConfigs(inst, config_area)

+     config = configs.get(args.NAME)

+ 

+     if not config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

+     if args and args.json:

+         o_str = config.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(config.display())

+ 

+ 

+ def mep_config_del(inst, basedn, log, args):

+     log = log.getChild('mep_config_del')

+     plugin = ManagedEntriesPlugin(inst)

+     config_area = plugin.get_attr_val_utf8_l('nsslapd-pluginConfigArea')

+     configs = MEPConfigs(inst, config_area)

+     config = configs.get(args.NAME)

+     config.delete()

+     log.info("Successfully deleted the %s", config.dn)

+ 

+ 

+ def mep_template_list(inst, basedn, log, args):

+     log = log.getChild('mep_template_list')

+     templates = MEPTemplates(inst, args.BASEDN)

+     result = []

+     result_json = []

+     for template in templates.list():

+         if args.json:

+             result_json.append(template.get_all_attrs_json())

+         else:

+             result.append(template.rdn)

+     if args.json:

+         print(json.dumps({"type": "list", "items": result_json}))

+     else:

+         if len(result) > 0:

+             for i in result:

+                 print(i)

+         else:

+             print("No Linked Attributes plugin instances")

+ 

+ 

+ def mep_template_add(inst, basedn, log, args):

+     log = log.getChild('mep_template_add')

+     targetdn = args.DN

+     generic_object_add(MEPTemplate, inst, log, args, arg_to_attr_config, dn=targetdn)

+     log.info('Don\'t forget to assign the template to Managed Entry Plugin config '

+              'attribute - managedTemplate')

+ 

+ 

+ def mep_template_edit(inst, basedn, log, args):

+     log = log.getChild('mep_template_edit')

+     targetdn = args.DN

+     templates = MEPTemplates(inst)

+     template = templates.get(targetdn)

+     generic_object_edit(template, log, args, arg_to_attr_config)

+ 

+ 

+ def mep_template_show(inst, basedn, log, args):

+     log = log.getChild('mep_template_show')

+     targetdn = args.DN

+     templates = MEPTemplates(inst)

+     template = templates.get(targetdn)

+ 

+     if not template.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)

+     if args and args.json:

+         o_str = template.get_all_attrs_json()

+         print(o_str)

+     else:

+         print(template.display())

+ 

+ 

+ def mep_template_del(inst, basedn, log, args):

+     log = log.getChild('mep_template_del')

+     targetdn = args.DN

+     templates = MEPTemplates(inst)

+     template = templates.get(targetdn)

+     template.delete()

+     log.info("Successfully deleted the %s", targetdn)

+ 

+ 

+ def _add_parser_args_config(parser):

+     parser.add_argument('--scope', help='Sets the scope of the search to use to see '

+                                         'which entries the plug-in monitors (originScope)')

+     parser.add_argument('--filter', help='Sets the search filter to use to search for and identify the entries '

+                                          'within the subtree which require a managed entry (originFilter)')

+     parser.add_argument('--managed-base', help='Sets the subtree under which to create '

+                                                'the managed entries (managedBase)')

+     parser.add_argument('--managed-template', help='Identifies the template entry to use to create '

+                                                    'the managed entry (managedTemplate)')

+ 

+ 

+ def _add_parser_args_template(parser):

+     parser.add_argument('--rdn-attr', help='Sets which attribute to use as the naming attribute '

+                                            'in the automatically-generated entry (mepRDNAttr)')

+     parser.add_argument('--static-attr', help='Sets an attribute with a defined value that must be added '

+                                               'to the automatically-generated entry (mepStaticAttr)')

+     parser.add_argument('--mapped-attr', nargs='+',

+                         help='Sets an attribute in the Managed Entries template entry which must exist '

+                              'in the generated entry (mepMappedAttr)')

  

  

  def create_parser(subparsers):

-     managedentries_parser = subparsers.add_parser('managedentries', help='Manage and configure Managed Entries plugin')

-     subcommands = managedentries_parser.add_subparsers(help='action')

+     mep = subparsers.add_parser('managed-entries', help='Manage and configure Managed Entries Plugin')

+     subcommands = mep.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, ManagedEntriesPlugin)

+ 

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=mep_edit)

+     edit.add_argument('--config-area', help='The value to set as nsslapd-pluginConfigArea')

+ 

+     list = subcommands.add_parser('list', help='List Managed Entries Plugin configs and templates')

+     subcommands_list = list.add_subparsers(help='action')

+     list_configs = subcommands_list.add_parser('configs', help='List Managed Entries Plugin configs (list config-area '

+                                                                'if specified in the main plugin entry)')

+     list_configs.set_defaults(func=mep_config_list)

+     list_templates = subcommands_list.add_parser('templates',

+                                                help='List Managed Entries Plugin templates in the directory')

+     list_templates.add_argument('BASEDN', help='The base DN where to search the templates.')

+     list_templates.set_defaults(func=mep_template_list)

+ 

+     config = subcommands.add_parser('config', help='Handle Managed Entries Plugin configs')

+     config.add_argument('NAME', help='The config entry CN.')

+     config_subcommands = config.add_subparsers(help='action')

+     add = config_subcommands.add_parser('add', help='Add the config entry')

+     add.set_defaults(func=mep_config_add)

+     _add_parser_args_config(add)

+     edit = config_subcommands.add_parser('set', help='Edit the config entry')

+     edit.set_defaults(func=mep_config_edit)

+     _add_parser_args_config(edit)

+     show = config_subcommands.add_parser('show', help='Display the config entry')

+     show.set_defaults(func=mep_config_show)

+     delete = config_subcommands.add_parser('delete', help='Delete the config entry')

+     delete.set_defaults(func=mep_config_del)

+ 

+     template = subcommands.add_parser('template', help='Handle Managed Entries Plugin templates')

+     template.add_argument('DN', help='The template entry DN.')

+     template_subcommands = template.add_subparsers(help='action')

+     add = template_subcommands.add_parser('add', help='Add the template entry')

+     add.set_defaults(func=mep_template_add)

+     _add_parser_args_template(add)

+     edit = template_subcommands.add_parser('set', help='Edit the template entry')

+     edit.set_defaults(func=mep_template_edit)

+     _add_parser_args_template(edit)

+     show = template_subcommands.add_parser('show', help='Display the template entry')

+     show.set_defaults(func=mep_template_show)

+     delete = template_subcommands.add_parser('delete', help='Delete the template entry')

+     delete.set_defaults(func=mep_template_del)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # Copyright (C) 2019 William Brown <william@blackhats.net.au>

  # All rights reserved.

  #
@@ -8,7 +8,7 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  import ldap

- from lib389.plugins import MemberOfPlugin, Plugins, MemberOfSharedConfig

+ from lib389.plugins import MemberOfPlugin, Plugins, MemberOfSharedConfig, MemberOfSharedConfigs

  from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

  

  arg_to_attr = {
@@ -26,18 +26,15 @@ 

  

  def memberof_edit(inst, basedn, log, args):

      log = log.getChild('memberof_edit')

-     plugins = Plugins(inst)

-     plugin = plugins.get("MemberOf Plugin")

+     plugin = MemberOfPlugin(inst)

      generic_object_edit(plugin, log, args, arg_to_attr)

  

  

  def memberof_add_config(inst, basedn, log, args):

      log = log.getChild('memberof_add_config')

      targetdn = args.DN

-     config = MemberOfSharedConfig(inst, targetdn)

-     generic_object_add(config, log, args, arg_to_attr)

-     plugins = Plugins(inst)

-     plugin = plugins.get("MemberOf Plugin")

+     config = generic_object_add(MemberOfSharedConfig, inst, log, args, arg_to_attr, dn=targetdn)

+     plugin = MemberOfPlugin(inst)

      plugin.replace('nsslapd-pluginConfigArea', config.dn)

      log.info('MemberOf attribute nsslapd-pluginConfigArea (config-entry) '

               'was set in the main plugin config')
@@ -87,50 +84,60 @@ 

  

  

  def _add_parser_args(parser):

-     parser.add_argument('--attr', nargs='+', help='The value to set as memberOfAttr')

-     parser.add_argument('--groupattr', nargs='+', help='The value to set as memberOfGroupAttr')

+     parser.add_argument('--attr', nargs='+',

+                         help='Specifies the attribute in the user entry for the Directory Server '

+                              'to manage to reflect group membership (memberOfAttr)')

+     parser.add_argument('--groupattr', nargs='+',

+                         help='Specifies the attribute in the group entry to use to identify '

+                              'the DNs of group members (memberOfGroupAttr)')

      parser.add_argument('--allbackends', choices=['on', 'off'], type=str.lower,

-                         help='The value to set as memberOfAllBackends')

+                         help='Specifies whether to search the local suffix for user entries on '

+                              'all available suffixes (memberOfAllBackends)')

      parser.add_argument('--skipnested', choices=['on', 'off'], type=str.lower,

-                         help='The value to set as memberOfSkipNested')

-     parser.add_argument('--scope', help='The value to set as memberOfEntryScope')

-     parser.add_argument('--exclude', help='The value to set as memberOfEntryScopeExcludeSubtree')

-     parser.add_argument('--autoaddoc', type=str.lower, help='The value to set as memberOfAutoAddOC')

+                         help='Specifies wherher to skip nested groups or not (memberOfSkipNested)')

+     parser.add_argument('--scope', help='Specifies backends or multiple-nested suffixes '

+                                         'for the MemberOf plug-in to work on (memberOfEntryScope)')

+     parser.add_argument('--exclude', help='Specifies backends or multiple-nested suffixes '

+                                           'for the MemberOf plug-in to exclude (memberOfEntryScopeExcludeSubtree)')

+     parser.add_argument('--autoaddoc', type=str.lower,

+                         help='If an entry does not have an object class that allows the memberOf attribute '

+                              'then the memberOf plugin will automatically add the object class listed '

+                              'in the memberOfAutoAddOC parameter')

  

  

  def create_parser(subparsers):

-     memberof_parser = subparsers.add_parser('memberof', help='Manage and configure MemberOf plugin')

+     memberof = subparsers.add_parser('memberof', help='Manage and configure MemberOf plugin')

  

-     subcommands = memberof_parser.add_subparsers(help='action')

+     subcommands = memberof.add_subparsers(help='action')

  

      add_generic_plugin_parsers(subcommands, MemberOfPlugin)

  

-     edit_parser = subcommands.add_parser('edit', help='Edit the plugin')

-     edit_parser.set_defaults(func=memberof_edit)

-     _add_parser_args(edit_parser)

-     edit_parser.add_argument('--config-entry', help='The value to set as nsslapd-pluginConfigArea')

- 

-     config_parser = subcommands.add_parser('config-entry', help='Manage the config entry')

-     config_subcommands = config_parser.add_subparsers(help='action')

-     add_config_parser = config_subcommands.add_parser('add', help='Add the config entry')

-     add_config_parser.set_defaults(func=memberof_add_config)

-     add_config_parser.add_argument('DN', help='The config entry full DN')

-     _add_parser_args(add_config_parser)

-     edit_config_parser = config_subcommands.add_parser('edit', help='Edit the config entry')

-     edit_config_parser.set_defaults(func=memberof_edit_config)

-     edit_config_parser.add_argument('DN', help='The config entry full DN')

-     _add_parser_args(edit_config_parser)

-     show_config_parser = config_subcommands.add_parser('show', help='Display the config entry')

-     show_config_parser.set_defaults(func=memberof_show_config)

-     show_config_parser.add_argument('DN', help='The config entry full DN')

-     del_config_parser = config_subcommands.add_parser('delete', help='Delete the config entry')

-     del_config_parser.set_defaults(func=memberof_del_config)

-     del_config_parser.add_argument('DN', help='The config entry full DN')

- 

-     fixup_parser = subcommands.add_parser('fixup', help='Run the fix-up task for memberOf plugin')

-     fixup_parser.set_defaults(func=fixup)

-     fixup_parser.add_argument('DN', help="base DN that contains entries to fix up")

-     fixup_parser.add_argument('-f', '--filter',

-                               help='Filter for entries to fix up.\n If omitted, all entries with objectclass '

-                                    'inetuser/inetadmin/nsmemberof under the specified base will have '

-                                    'their memberOf attribute regenerated.')

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=memberof_edit)

+     _add_parser_args(edit)

+     edit.add_argument('--config-entry', help='The value to set as nsslapd-pluginConfigArea')

+ 

+     config = subcommands.add_parser('config-entry', help='Manage the config entry')

+     config_subcommands = config.add_subparsers(help='action')

+     add_config = config_subcommands.add_parser('add', help='Add the config entry')

+     add_config.set_defaults(func=memberof_add_config)

+     add_config.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(add_config)

+     edit_config = config_subcommands.add_parser('set', help='Edit the config entry')

+     edit_config.set_defaults(func=memberof_edit_config)

+     edit_config.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(edit_config)

+     show_config = config_subcommands.add_parser('show', help='Display the config entry')

+     show_config.set_defaults(func=memberof_show_config)

+     show_config.add_argument('DN', help='The config entry full DN')

+     del_config_ = config_subcommands.add_parser('delete', help='Delete the config entry')

+     del_config_.set_defaults(func=memberof_del_config)

+     del_config_.add_argument('DN', help='The config entry full DN')

+ 

+     fixup = subcommands.add_parser('fixup', help='Run the fix-up task for memberOf plugin')

+     fixup.set_defaults(func=fixup)

+     fixup.add_argument('DN', help="Base DN that contains entries to fix up")

+     fixup.add_argument('-f', '--filter',

+                        help='Filter for entries to fix up.\n If omitted, all entries with objectclass '

+                             'inetuser/inetadmin/nsmemberof under the specified base will have '

+                             'their memberOf attribute regenerated.')

@@ -1,16 +1,88 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

+ import json

+ import ldap

  from lib389.plugins import PassThroughAuthenticationPlugin

  from lib389.cli_conf import add_generic_plugin_parsers

  

  

+ def pta_list(inst, basedn, log, args):

+     log = log.getChild('pta_list')

+     plugin = PassThroughAuthenticationPlugin(inst)

+     result = []

+     urls = plugin.get_urls()

+     if args.json:

+         print(json.dumps({"type": "list", "items": urls}))

+     else:

+         if len(urls) > 0:

+             for i in result:

+                 print(i)

+         else:

+             print("No Pass Through Auth attributes were found")

+ 

+ 

+ def pta_add(inst, basedn, log, args):

+     log = log.getChild('pta_add')

+     plugin = PassThroughAuthenticationPlugin(inst)

+     urls = list(map(lambda url: url.lower(), plugin.get_urls()))

+     if args.URL.lower() in urls:

+         raise ldap.ALREADY_EXISTS("Entry %s already exists" % args.URL)

+     plugin.add("nsslapd-pluginarg%s" % len(urls), args.URL)

+ 

+ 

+ def pta_edit(inst, basedn, log, args):

+     log = log.getChild('pta_edit')

+     plugin = PassThroughAuthenticationPlugin(inst)

+     urls = list(map(lambda url: url.lower(), plugin.get_urls()))

+     old_url_l = args.OLD_URL.lower()

+     if old_url_l not in urls:

+         log.info("Entry %s doesn't exists. Adding a new value." % args.OLD_URL)

+         url_num = len(urls)

+     else:

+         url_num = urls.index(old_url_l)

+         plugin.remove("nsslapd-pluginarg%s" % url_num, old_url_l)

+     plugin.add("nsslapd-pluginarg%s" % url_num, args.NEW_URL)

+ 

+ 

+ def pta_del(inst, basedn, log, args):

+     log = log.getChild('pta_del')

+     plugin = PassThroughAuthenticationPlugin(inst)

+     urls = list(map(lambda url: url.lower(), plugin.get_urls()))

+     old_url_l = args.URL.lower()

+     if old_url_l not in urls:

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.URL)

+ 

+     plugin.remove_all("nsslapd-pluginarg%s" % urls.index(old_url_l))

+     log.info("Successfully deleted %s", args.URL)

+ 

+ 

  def create_parser(subparsers):

-     passthroughauth_parser = subparsers.add_parser('passthroughauth', help='Manage and configure Pass-Through Authentication plugin')

+     passthroughauth_parser = subparsers.add_parser('pass-through-auth', help='Manage and configure Pass-Through Authentication plugin')

      subcommands = passthroughauth_parser.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, PassThroughAuthenticationPlugin)

+ 

+     list = subcommands.add_parser('list', help='List available plugin configs')

+     list.set_defaults(func=pta_list)

+ 

+     add = subcommands.add_parser('add', help='Add the config entry')

+     add.add_argument('URL', help='The full LDAP URL in format '

+                                   '"ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS". '

+                                   'If one optional parameter is specified the rest should be specified too')

+     add.set_defaults(func=pta_add)

+ 

+     edit = subcommands.add_parser('modify', help='Edit the config entry')

+     edit.add_argument('OLD_URL', help='The full LDAP URL you get from the "list" command')

+     edit.add_argument('NEW_URL', help='The full LDAP URL in format '

+                                  '"ldap|ldaps://authDS/subtree maxconns,maxops,timeout,ldver,connlifetime,startTLS". '

+                                  'If one optional parameter is specified the rest should be specified too')

+     edit.set_defaults(func=pta_edit)

+ 

+     delete = subcommands.add_parser('delete', help='Delete the config entry')

+     delete.add_argument('URL', help='The full LDAP URL you get from the "list" command')

+     delete.set_defaults(func=pta_del)

@@ -1,197 +1,57 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 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 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')

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit

  

- 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)

+ arg_to_attr = {

+     'update_delay': 'referint-update-delay',

+     'membership_attr': 'referint-membership-attr',

+     'entry_scope': 'nsslapd-pluginEntryScope',

+     'exclude_entry_scope': 'nsslapd-pluginExcludeEntryScope',

+     'container_scope': 'nsslapd-pluginContainerScope',

+ }

  

- 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):

+ def referint_edit(inst, basedn, log, args):

+     log = log.getChild('referint_edit')

      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))

+     generic_object_edit(plugin, log, args, arg_to_attr)

  

- 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 _add_parser_args(parser):

+     parser.add_argument('--update-delay',

+                         help='Sets the update interval. Special values: 0 - The check is performed immediately, '

+                              '-1 - No check is performed (referint-update-delay)')

+     parser.add_argument('--membership-attr', nargs='+',

+                         help='Specifies attributes to check for and update (referint-membership-attr)')

+     parser.add_argument('--entry-scope',

+                         help='Defines the subtree in which the plug-in looks for the delete '

+                              'or rename operations of a user entry (nsslapd-pluginEntryScope)')

+     parser.add_argument('--exclude-entry-scope',

+                         help='Defines the subtree in which the plug-in ignores any operations '

+                              'for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)')

+     parser.add_argument('--container_scope',

+                         help='Specifies which branch the plug-in searches for the groups to which the user belongs. '

+                              'It only updates groups that are under the specified container branch, '

+                              'and leaves all other groups not updated (nsslapd-pluginContainerScope)')

  

  

  def create_parser(subparsers):

-     referint_parser = subparsers.add_parser('referint', help='Manage and configure Referential Integrity plugin')

+     referint = subparsers.add_parser('referential-integrity',

+                                       help='Manage and configure Referential Integrity Postoperation plugin')

  

-     subcommands = referint_parser.add_subparsers(help='action')

+     subcommands = referint.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)

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=referint_edit)

+     _add_parser_args(edit)

  

-     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)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -7,10 +7,44 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  from lib389.plugins import RetroChangelogPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit

+ 

+ arg_to_attr = {

+     'is-replicated': 'isReplicated',

+     'attribute': 'nsslapd-attribute',

+     'directory': 'nsslapd-changelogdir',

+     'max-age': 'nsslapd-changelogmaxage',

+ }

+ 

+ 

+ def retrochangelog_edit(inst, basedn, log, args):

+     log = log.getChild('retrochangelog_edit')

+     plugin = RetroChangelogPlugin(inst)

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('--is-replicated', choices=['true', 'false'],

+                         help='Sets a flag to indicate on a change in the changelog whether the change is newly made '

+                              'on that server or whether it was replicated over from another server (isReplicated)')

+     parser.add_argument('--attribute',

+                         help='Specifies another Directory Server attribute which must be included in '

+                              'the retro changelog entries (nsslapd-attribute)')

+     parser.add_argument('--directory',

+                         help='Specifies the name of the directory in which the changelog database '

+                              'is created the first time the plug-in is run')

+     parser.add_argument('--max-age',

+                         help='This attribute specifies the maximum age of any entry '

+                              'in the changelog (nsslapd-changelogmaxage)')

  

  

  def create_parser(subparsers):

-     retrochangelog_parser = subparsers.add_parser('retrochangelog', help='Manage and configure Retro Changelog plugin')

-     subcommands = retrochangelog_parser.add_subparsers(help='action')

+     retrochangelog = subparsers.add_parser('retro-changelog', help='Manage and configure Retro Changelog plugin')

+     subcommands = retrochangelog.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, RetroChangelogPlugin)

+ 

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=retrochangelog_edit)

+     _add_parser_args(edit)

+ 

+ 

@@ -1,229 +1,68 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 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 RootDNAccessControlPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

- 

- 

- def display_time(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     val = plugin.get_open_time_formatted()

-     if not val:

-         log.info("rootdn-open-time is not set")

-     else:

-         log.info(val)

-     val = plugin.get_close_time_formatted()

-     if not val:

-         log.info("rootdn-close-time is not set")

-     else:

-         log.info(val)

- 

- def set_open_time(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.set_open_time(args.value)

-     log.info('rootdn-open-time set to "{}"'.format(args.value))

- 

- def set_close_time(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.set_close_time(args.value)

-     log.info('rootdn-close-time set to "{}"'.format(args.value))

- 

- def clear_time(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.remove_open_time()

-     plugin.remove_close_time()

-     log.info('time-based policy was cleared')

- 

- def display_ips(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     allowed_ips = plugin.get_allow_ip_formatted()

-     denied_ips = plugin.get_deny_ip_formatted()

-     if not allowed_ips and not denied_ips:

-         log.info("No ip-based access control policy has been configured")

-     else:

-         log.info(allowed_ips)

-         log.info(denied_ips)

- 

- def allow_ip(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

- 

-     # remove ip from denied ips

-     try:

-         plugin.remove_deny_ip(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         pass

- 

-     try:

-         plugin.add_allow_ip(args.value)

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         pass

-     log.info('{} added to rootdn-allow-ip'.format(args.value))

- 

- def deny_ip(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

- 

-     # remove ip from allowed ips

-     try:

-         plugin.remove_allow_ip(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         pass

- 

-     try:

-         plugin.add_deny_ip(args.value)

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         pass

-     log.info('{} added to rootdn-deny-ip'.format(args.value))

- 

- def clear_all_ips(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.remove_all_allow_ip()

-     plugin.remove_all_deny_ip()

-     log.info('ip-based policy was cleared')

- 

- def display_hosts(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     allowed_hosts = plugin.get_allow_host_formatted()

-     denied_hosts = plugin.get_deny_host_formatted()

-     if not allowed_hosts and not denied_hosts:

-         log.info("No host-based access control policy has been configured")

-     else:

-         log.info(allowed_hosts)

-         log.info(denied_hosts)

- 

- def allow_host(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

- 

-     # remove host from denied hosts

-     try:

-         plugin.remove_deny_host(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         pass

- 

-     try:

-         plugin.add_allow_host(args.value)

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         pass

-     log.info('{} added to rootdn-allow-host'.format(args.value))

- 

- def deny_host(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

- 

-     # remove host from allowed hosts

-     try:

-         plugin.remove_allow_host(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         pass

- 

-     try:

-         plugin.add_deny_host(args.value)

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         pass

-     log.info('{} added to rootdn-deny-host'.format(args.value))

- 

- def clear_all_hosts(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.remove_all_allow_host()

-     plugin.remove_all_deny_host()

-     log.info('host-based policy was cleared')

- 

- def display_days(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     days = plugin.get_days_allowed_formatted()

-     if not days:

-         log.info("No day-based access control policy has been configured")

-     else:

-         log.info(days)

- 

- def allow_day(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     args.value = args.value[0:3]

-     plugin.add_allow_day(args.value)

-     log.info('{} added to rootdn-days-allowed'.format(args.value))

- 

- def deny_day(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     args.value = args.value[0:3]

-     plugin.remove_allow_day(args.value)

-     log.info('{} removed from rootdn-days-allowed'.format(args.value))

- 

- def clear_all_days(inst, basedn, log, args):

-     plugin = RootDNAccessControlPlugin(inst)

-     plugin.remove_days_allowed()

-     log.info('day-based policy was cleared')

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit

+ 

+ arg_to_attr = {

+     'allow_host': 'rootdn-allow-host',

+     'deny_host': 'rootdn-deny-host',

+     'allow_ip': 'rootdn-allow-ip',

+     'deny_ip': 'rootdn-deny-ip',

+     'open_time': 'rootdn-open-time',

+     'close_time': 'rootdn-close-time',

+     'days_allowed': 'rootdn-days-allowed'

+ }

+ 

+ 

+ def rootdn_edit(inst, basedn, log, args):

+     log = log.getChild('rootdn_edit')

+     plugin = RootDNAccessControlPlugin(inst)

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('--allow-host',

+                         help='Sets what hosts, by fully-qualified domain name, the root user is allowed to use '

+                              'to access the Directory Server. Any hosts not listed are implicitly denied '

+                              '(rootdn-allow-host)')

+     parser.add_argument('--deny-host',

+                         help='Sets what hosts, by fully-qualified domain name, the root user is not allowed to use '

+                              'to access the Directory Server Any hosts not listed are implicitly allowed '

+                              '(rootdn-deny-host). If an host address is listed in both the rootdn-allow-host and '

+                              'rootdn-deny-host attributes, it is denied access.')

+     parser.add_argument('--allow-ip',

+                         help='Sets what IP addresses, either IPv4 or IPv6, for machines the root user is allowed '

+                              'to use to access the Directory Server Any IP addresses not listed are implicitly '

+                              'denied (rootdn-allow-ip)')

+     parser.add_argument('--deny-ip',

+                         help='Sets what IP addresses, either IPv4 or IPv6, for machines the root user is not allowed '

+                              'to use to access the Directory Server. Any IP addresses not listed are implicitly '

+                              'allowed (rootdn-deny-ip) If an IP address is listed in both the rootdn-allow-ip and '

+                              'rootdn-deny-ip attributes, it is denied access.')

+     parser.add_argument('--open-time',

+                         help='Sets part of a time period or range when the root user is allowed to access '

+                              'the Directory Server. This sets when the time-based access begins (rootdn-open-time)')

+     parser.add_argument('--close-time',

+                         help='Sets part of a time period or range when the root user is allowed to access '

+                              'the Directory Server. This sets when the time-based access ends (rootdn-close-time)')

+     parser.add_argument('--days-allowed',

+                         help='Gives a comma-separated list of what days the root user is allowed to use to access '

+                              'the Directory Server. Any days listed are implicitly denied (rootdn-days-allowed)')

  

  

  def create_parser(subparsers):

-     rootdnac_parser = subparsers.add_parser('rootdn', help='Manage and configure RootDN Access Control plugin')

+     rootdnac_parser = subparsers.add_parser('root-dn', help='Manage and configure RootDN Access Control plugin')

      subcommands = rootdnac_parser.add_subparsers(help='action')

      add_generic_plugin_parsers(subcommands, RootDNAccessControlPlugin)

  

-     time_parser = subcommands.add_parser('time', help='get or set rootdn open and close times')

-     time_parser.set_defaults(func=display_time)

- 

-     time_subcommands = time_parser.add_subparsers(help='action')

- 

-     open_time_parser = time_subcommands.add_parser('open', help='set open time value')

-     open_time_parser.set_defaults(func=set_open_time)

-     open_time_parser.add_argument('value', help='Value to set as open time')

- 

-     close_time_parser = time_subcommands.add_parser('close', help='set close time value')

-     close_time_parser.set_defaults(func=set_close_time)

-     close_time_parser.add_argument('value', help='Value to set as close time')

- 

-     time_clear_parser = time_subcommands.add_parser('clear', help='reset time-based access policy')

-     time_clear_parser.set_defaults(func=clear_time)

- 

-     ip_parser = subcommands.add_parser('ip', help='get or set ip access policy')

-     ip_parser.set_defaults(func=display_ips)

- 

-     ip_subcommands = ip_parser.add_subparsers(help='action')

- 

-     ip_allow_parser = ip_subcommands.add_parser('allow', help='allow IP addr or IP addr range')

-     ip_allow_parser.set_defaults(func=allow_ip)

-     ip_allow_parser.add_argument('value', help='IP addr or IP addr range')

- 

-     ip_deny_parser = ip_subcommands.add_parser('deny', help='deny IP addr or IP addr range')

-     ip_deny_parser.set_defaults(func=deny_ip)

-     ip_deny_parser.add_argument('value', help='IP addr or IP addr range')

- 

-     ip_clear_parser = ip_subcommands.add_parser('clear', help='reset IP-based access policy')

-     ip_clear_parser.set_defaults(func=clear_all_ips)

- 

-     host_parser = subcommands.add_parser('host', help='get or set host access policy')

-     host_parser.set_defaults(func=display_hosts)

- 

-     host_subcommands = host_parser.add_subparsers(help='action')

- 

-     host_allow_parser = host_subcommands.add_parser('allow', help='allow host address')

-     host_allow_parser.set_defaults(func=allow_host)

-     host_allow_parser.add_argument('value', help='host address')

- 

-     host_deny_parser = host_subcommands.add_parser('deny', help='deny host address')

-     host_deny_parser.set_defaults(func=deny_host)

-     host_deny_parser.add_argument('value', help='host address')

- 

-     host_clear_parser = host_subcommands.add_parser('clear', help='reset host-based access policy')

-     host_clear_parser.set_defaults(func=clear_all_hosts)

- 

-     day_parser = subcommands.add_parser('day', help='get or set days access policy')

-     day_parser.set_defaults(func=display_days)

- 

-     day_subcommands = day_parser.add_subparsers(help='action')

- 

-     day_allow_parser = day_subcommands.add_parser('allow', help='allow day of the week')

-     day_allow_parser.set_defaults(func=allow_day)

-     day_allow_parser.add_argument('value', type=str.capitalize, help='day of the week')

+     edit = subcommands.add_parser('set', help='Edit the plugin')

+     edit.set_defaults(func=rootdn_edit)

+     _add_parser_args(edit)

  

-     day_deny_parser = day_subcommands.add_parser('deny', help='deny day of the week')

-     day_deny_parser.set_defaults(func=deny_day)

-     day_deny_parser.add_argument('value', type=str.capitalize, help='day of the week')

  

-     day_clear_parser = day_subcommands.add_parser('clear', help='reset day-based access policy')

-     day_clear_parser.set_defaults(func=clear_all_days)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

+ # Copyright (C) 2019 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -17,40 +17,55 @@ 

      else:

          log.info("USN global mode is disabled")

  

+ 

  def enable_global_mode(inst, basedn, log, args):

      plugin = USNPlugin(inst)

      plugin.enable_global_mode()

      log.info("USN global mode enabled")

  

+ 

  def disable_global_mode(inst, basedn, log, args):

      plugin = USNPlugin(inst)

      plugin.disable_global_mode()

      log.info("USN global mode disabled")

  

+ 

  def tombstone_cleanup(inst, basedn, log, args):

      plugin = USNPlugin(inst)

-     log.info('Attempting to add task entry... This will fail if replication is enabled or if USN plug-in is disabled.')

+     log.info('Attempting to add task entry...')

+     if not plugin.status():

+         log.error("'%s' is disabled. Fix up task can't be executed" % plugin.rdn)

      task = plugin.cleanup(args.suffix, args.backend, args.maxusn)

-     log.info('Successfully added task entry ' + task.dn)

+     task.wait()

+     exitcode = task.get_exit_code()

+     if exitcode != 0:

+         log.error('USM tombstone cleanup task has failed. Please, check logs')

+     else:

+         log.info('Successfully added task entry')

+ 

  

  def create_parser(subparsers):

      usn_parser = subparsers.add_parser('usn', help='Manage and configure USN plugin')

- 

      subcommands = usn_parser.add_subparsers(help='action')

- 

      add_generic_plugin_parsers(subcommands, USNPlugin)

  

-     global_mode_parser = subcommands.add_parser('global', help='get or manage global usn mode')

+     global_mode_parser = subcommands.add_parser('global', help='Get or manage global usn mode (nsslapd-entryusn-global)')

      global_mode_parser.set_defaults(func=display_usn_mode)

      global_mode_subcommands = global_mode_parser.add_subparsers(help='action')

-     on_global_mode_parser = global_mode_subcommands.add_parser('on', help='enable usn global mode')

+     on_global_mode_parser = global_mode_subcommands.add_parser('on', help='Enable usn global mode')

      on_global_mode_parser.set_defaults(func=enable_global_mode)

-     off_global_mode_parser = global_mode_subcommands.add_parser('off', help='disable usn global mode')

+     off_global_mode_parser = global_mode_subcommands.add_parser('off', help='Disable usn global mode')

      off_global_mode_parser.set_defaults(func=disable_global_mode)

  

-     cleanup_parser = subcommands.add_parser('cleanup', help='run the USN tombstone cleanup task')

+     cleanup_parser = subcommands.add_parser('cleanup', help='Run the USN tombstone cleanup task')

      cleanup_parser.set_defaults(func=tombstone_cleanup)

      cleanup_group = cleanup_parser.add_mutually_exclusive_group(required=True)

-     cleanup_group.add_argument('-s', '--suffix', help="suffix where USN tombstone entries are cleaned up")

-     cleanup_group.add_argument('-n', '--backend', help="backend instance in which USN tombstone entries are cleaned up (alternative to suffix)")

-     cleanup_parser.add_argument('-m', '--maxusn', type=int, help="USN tombstone entries are deleted up to the entry with maxusn")

+     cleanup_group.add_argument('-s', '--suffix',

+                                help='Gives the suffix or subtree in the Directory Server to run the cleanup operation '

+                                     'against. If the suffix is not specified, then the back end must be given (suffix)')

+     cleanup_group.add_argument('-n', '--backend',

+                                help='Gives the Directory Server instance back end, or database, to run the cleanup '

+                                     'operation against. If the back end is not specified, then the suffix must be '

+                                     'specified.Backend instance in which USN tombstone entries (backend)')

+     cleanup_parser.add_argument('-m', '--maxusn', type=int, help='Gives the highest USN value to delete when '

+                                                                  'removing tombstone entries (max_usn_to_delete)')

@@ -1,16 +0,0 @@ 

- # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2018 Red Hat, Inc.

- # All rights reserved.

- #

- # License: GPL (version 3 or any later version).

- # See LICENSE for details.

- # --- END COPYRIGHT BLOCK ---

- 

- from lib389.plugins import WhoamiPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

- 

- 

- def create_parser(subparsers):

-     whoami_parser = subparsers.add_parser('whoami', help='Manage and configure whoami plugin')

-     subcommands = whoami_parser.add_subparsers(help='action')

-     add_generic_plugin_parsers(subcommands, WhoamiPlugin)

@@ -199,8 +199,8 @@ 

      set_parser.add_argument('--pwdlockout', help="Set to \"on\" to enable account lockout")

      set_parser.add_argument('--pwdunlock', help="Set to \"on\" to allow an account to become unlocked after the lockout duration")

      set_parser.add_argument('--pwdlockoutduration', help="The number of seconds an account stays locked out")

-     set_parser.add_argument('--pwdmaxfailures', help="The maximum number of allowed failed password attempts beforet the acocunt gets locked")

-     set_parser.add_argument('--pwdresetfailcount', help="The number of secondsto wait before reducingthe failed login count on an account")

+     set_parser.add_argument('--pwdmaxfailures', help="The maximum number of allowed failed password attempts before the account gets locked")

+     set_parser.add_argument('--pwdresetfailcount', help="The number of seconds to wait before reducing the failed login count on an account")

      # Syntax settings

      set_parser.add_argument('--pwdchecksyntax', help="Set to \"on\" to Enable password syntax checking")

      set_parser.add_argument('--pwdminlen', help="The minimum number of characters required in a password")

file modified
+205 -3
@@ -6,6 +6,7 @@ 

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

+ import collections

  import ldap

  import copy

  import os.path
@@ -13,6 +14,7 @@ 

  from lib389 import tasks

  from lib389._mapped_object import DSLdapObjects, DSLdapObject

  from lib389.lint import DSRILE0001

+ from lib389.utils import ensure_str, ensure_list_bytes

  from lib389._constants import DN_PLUGIN

  from lib389.properties import (

          PLUGINS_OBJECTCLASS_VALUE, PLUGIN_PROPNAME_TO_ATTRNAME,
@@ -158,6 +160,72 @@ 

          self.set('uniqueness-across-all-subtrees', 'off')

  

  

+ class AttributeUniquenessPlugins(DSLdapObjects):

+     """A DSLdapObjects entity which represents Attribute Uniqueness plugin instances

+ 

+     :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="cn=plugins,cn=config"):

+         super(Plugins, self).__init__(instance=instance)

+         self._objectclasses = ['top', 'nsslapdplugin']

+         self._filterattrs = ['cn', 'nsslapd-pluginPath']

+         self._childobject = AttributeUniquenessPlugin

+         self._basedn = basedn

+         # This is used to allow entry to instance to work

+         self._list_attrlist = ['dn', 'nsslapd-pluginPath']

+         self._search_filter = "(nsslapd-pluginId=NSUniqueAttr)"

+ 

+     def list(self):

+         """Get a list of all plugin instances where nsslapd-pluginId: NSUniqueAttr

+ 

+         :returns: A list of children entries

+         """

+ 

+         try:

+             results = self._instance.search_ext_s(

+                 base=self._basedn,

+                 scope=ldap.SCOPE_ONELEVEL,

+                 filterstr=self._search_filter,

+                 attrlist=self._list_attrlist,

+                 serverctrls=self._server_controls, clientctrls=self._client_controls

+             )

+             insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results]

+         except ldap.NO_SUCH_OBJECT:

+             # There are no objects to select from, se we return an empty array

+             insts = []

+         return insts

+ 

+     def _get_dn(self, dn):

+         # This will yield and & filter for objectClass with as many terms as needed.

+         self._log.debug('_gen_dn filter = %s' % self._search_filter)

+         self._log.debug('_gen_dn dn = %s' % dn)

+         return self._instance.search_ext_s(

+             base=dn,

+             scope=ldap.SCOPE_BASE,

+             filterstr=self._search_filter,

+             attrlist=self._list_attrlist,

+             serverctrls=self._server_controls, clientctrls=self._client_controls

+         )

+ 

+     def _get_selector(self, selector):

+         # Filter based on the objectclasses and the basedn

+         # Based on the selector, we should filter on that too.

+         # This will yield and & filter for objectClass with as many terms as needed.

+         filterstr = "&(cn=%s)%s" % (selector, self._search_filter)

+         self._log.debug('_gen_selector filter = %s' % filterstr)

+         return self._instance.search_ext_s(

+             base=self._basedn,

+             scope=self._scope,

+             filterstr=filterstr,

+             attrlist=self._list_attrlist,

+             serverctrls=self._server_controls, clientctrls=self._client_controls

+         )

+ 

+ 

  class LdapSSOTokenPlugin(Plugin):

      """An instance of ldapssotoken plugin entry

  
@@ -222,11 +290,14 @@ 

      :type basedn: str

      """

  

-     def __init__(self, instance, basedn="cn=managed entries,cn=plugins,cn=config"):

+     def __init__(self, instance, basedn=None):

          super(MEPConfigs, self).__init__(instance)

          self._objectclasses = ['top', 'extensibleObject']

          self._filterattrs = ['cn']

          self._childobject = MEPConfig

+         # So we can set the configArea easily

+         if basedn is None:

+             basedn = "cn=managed entries,cn=plugins,cn=config"

          self._basedn = basedn

  

  
@@ -243,7 +314,7 @@ 

          super(MEPTemplate, self).__init__(instance, dn)

          self._rdn_attribute = 'cn'

          self._must_attributes = ['cn']

-         self._create_objectclasses = ['top', 'extensibleObject', 'mepTemplateEntry']

+         self._create_objectclasses = ['top', 'mepTemplateEntry']

          self._protected = False

  

  
@@ -258,7 +329,7 @@ 

  

      def __init__(self, instance, basedn):

          super(MEPTemplates, self).__init__(instance)

-         self._objectclasses = ['top', 'extensibleObject']

+         self._objectclasses = ['top', 'mepTemplateEntry']

          self._filterattrs = ['cn']

          self._childobject = MEPTemplate

          self._basedn = basedn
@@ -774,6 +845,23 @@ 

          self._exit_code = None

  

  

+ class MemberOfSharedConfigs(DSLdapObjects):

+     """A DSLdapObjects entity which represents MemberOf config 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=None):

+         super(MemberOfSharedConfigs, self).__init__(instance)

+         self._objectclasses = ['top', 'extensibleObject']

+         self._filterattrs = ['cn']

+         self._childobject = MemberOfSharedConfig

+         self._basedn = basedn

+ 

+ 

  class RetroChangelogPlugin(Plugin):

      """An instance of Retro Changelog plugin entry

  
@@ -1163,6 +1251,19 @@ 

      def __init__(self, instance, dn="cn=Pass Through Authentication,cn=plugins,cn=config"):

          super(PassThroughAuthenticationPlugin, self).__init__(instance, dn)

  

+     def get_urls(self):

+         """Get all URLs from nsslapd-pluginargNUM attributes

+ 

+         :returns: a list

+         """

+ 

+         attr_dict = collections.OrderedDict(sorted(self.get_all_attrs().items()))

+         result = []

+         for attr, value in attr_dict.items():

+             if attr.startswith("nsslapd-pluginarg"):

+                 result.append(ensure_str(value[0]))

+         return result

+ 

  

  class USNPlugin(Plugin):

      """A single instance of USN (Update Sequence Number) plugin entry
@@ -1629,6 +1730,107 @@ 

          self._basedn = basedn

  

  

+ class DNAPluginSharedConfig(DSLdapObject):

+     """A single instance of DNA Plugin config entry

+ 

+     :param instance: An instance

+     :type instance: lib389.DirSrv

+     :param dn: Entry DN

+     :type dn: str

+     """

+ 

+     def __init__(self, instance, dn=None):

+         super(DNAPluginSharedConfig, self).__init__(instance, dn)

+         self._rdn_attribute = 'dnaHostname'

+         self._must_attributes = ['dnaHostname', 'dnaPortNum']

+         self._create_objectclasses = ['top', 'dnaSharedConfig']

+         self._protected = False

+ 

+     def create(self, properties=None, basedn=None, ensure=False):

+         """The shared config DNA plugin entry has two RDN values

+         The function takes care about that special case

+         """

+ 

+         for attr in self._must_attributes:

+             if properties.get(attr, None) is None:

+                 raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr)

+ 

+         assert basedn is not None, "Base DN should be specified"

+ 

+         # Make a DN with the two items RDN and base DN

+         decomposed_dn = [[('dnaHostname', properties['dnaHostname'], 1),

+                           ('dnaPortNum', properties['dnaPortNum'], 1)]] + ldap.dn.str2dn(basedn)

+         dn = ldap.dn.dn2str(decomposed_dn)

+ 

+         exists = False

+         if ensure:

+             # If we are running in stateful ensure mode, we need to check if the object exists, and

+             # we can see the state that it is in.

+             try:

+                 self._instance.search_ext_s(dn, ldap.SCOPE_BASE, self._object_filter, attrsonly=1,

+                                             serverctrls=self._server_controls, clientctrls=self._client_controls,

+                                             escapehatch='i am sure')

+                 exists = True

+             except ldap.NO_SUCH_OBJECT:

+                 pass

+ 

+         if exists and ensure:

+             # update properties

+             self._log.debug('Exists %s' % dn)

+             self._dn = dn

+             # Now use replace_many to setup our values

+             mods = []

+             for k, v in list(properties.items()):

+                 mods.append((ldap.MOD_REPLACE, k, v))

+             self._instance.modify_ext_s(self._dn, mods, serverctrls=self._server_controls,

+                                         clientctrls=self._client_controls, escapehatch='i am sure')

+         else:

+             self._log.debug('Creating %s' % dn)

+             mods = [('objectclass', ensure_list_bytes(self._create_objectclasses))]

+             # Bring our mods to one type and do ensure bytes on the list

+             for attr, value in properties.items():

+                 if not isinstance(value, list):

+                     value = [value]

+                 mods.append((attr, ensure_list_bytes(value)))

+             # We rely on exceptions here to indicate failure to the parent.

+             self._log.debug('Creating entry %s : %s' % (dn, mods))

+             self._instance.add_ext_s(dn, mods, serverctrls=self._server_controls, clientctrls=self._client_controls,

+                                      escapehatch='i am sure')

+             # If it worked, we need to fix our instance dn

+             self._dn = dn

+ 

+         return self

+ 

+ 

+ class DNAPluginSharedConfigs(DSLdapObjects):

+     """A DSLdapObjects entity which represents DNA Plugin config 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=None):

+         super(DNAPluginSharedConfigs, self).__init__(instance)

+         self._objectclasses = ['top', 'dnaSharedConfig']

+         self._filterattrs = ['dnaHostname', 'dnaPortNum']

+         self._childobject = DNAPluginSharedConfig

+         self._basedn = basedn

+ 

+     def create(self, properties=None):

+         """Create an object under base DN of our entry

+ 

+         :param properties: Attributes for the new entry

+         :type properties: dict

+ 

+         :returns: DSLdapObject of the created entry

+         """

+ 

+         co = self._entry_to_instance(dn=None, entry=None)

+         return co.create(properties, self._basedn)

+ 

+ 

  class Plugins(DSLdapObjects):

      """A DSLdapObjects entity which represents plugin entry

  

Description: Add the functionality for
account-policy, attr-uniq, automember, dna, linked-attr,
managed-entries, memberof, pass-through-auth, refer-init,
retro-changelog, root-dn, usn commands.
Make DSLdapObject create an entry with only DN and attributes
(cases when RDN is not specified).

https://pagure.io/389-ds-base/issue/50041

Reviewed by: ?

This name is odd. "refer-init" specificity the "init" part is not correct. Perhaps "refer-int"?

typo, should this be: _add_parser_args_definition

Should this be a boolean, action store true? Or choices=['yes', 'no']?

So the main issue if the naming you used for the RI plugin. The function, variable, and file names should be changed to not use refer-init,(or referinit). The "init" part is wrong and should be "int" or something more verbose.

Everything else looks pretty good, I'll wrap up my review later today.

You can better supply these to ldap.dn.dn2str which takes List[List[Tuple]].

1 new commit added

  • Fix issues after PR comments
5 years ago

I think the others commented here, but I use referint as shorthand, for anything external it should be referrential-integrity.

If you move this up, then if a _dn is provided, it's wiped out if we generate content from the tdn based on other parameters. So I'm not sure about this change.

I think this is enabling laziness, and I don't really like it. LDAP rdns are "based on the content of the entry", and by having it so that we can "have the rdn influcence the content of the entry" I think we are undoing some of that modeling.

I think I would ask what issue this is attempting to solve, because I think this change may be reflective of a problem elsewhere.

I'm okay with this change, but Marc probably needs to know for docs ...

I don't like the use of the word "edit" here because I was reserving that for an interactive entry editor I was plannig to add. Could this actually be something else?

Did you want to add the "modify" subcommand too?

Tests for the new dna plugin config types?

Part of me wonders if this could actually be split to about 3 seperate patches (new plugin functionality, new cli functionality, new js integration). It's a pretty long patch ...

This name is odd. "refer-init" specificity the "init" part is not correct. Perhaps "refer-int"?

I am okay with it if nobody else doesn't mind.

I think this is enabling laziness, and I don't really like it. LDAP rdns are "based on the content of the entry", and by having it so that we can "have the rdn influcence the content of the entry" I think we are undoing some of that modeling.
I think I would ask what issue this is attempting to solve, because I think this change may be reflective of a problem elsewhere.

What I want to have is to be able to create an entry without specifying RDN in the parameters and take it from DN:

properties = {"specattrname": "acctPolicySubentry",
              "limitattrname": "accountInactivityLimit"}
config = AccountPolicyConfig(inst, targetdn)
config.create(properties)

As far as I understand, DN should always contain RDN which is part of the entry attributes.
We already generate DN out of RDN and Base DN. Why not to get RDN from DN? It will make some operations easier (like here, in CLI, when a user provides DN for the new entry)
Do I miss something?

I don't like the use of the word "edit" here because I was reserving that for an interactive entry editor I was plannig to add. Could this actually be something else?

I don't see any problem. I will change it to set word and it will still be accurate.

Did you want to add the "modify" subcommand too?

Nope, I think the set of commands fulfill the main needs.

Tests for the new dna plugin config types?

Sure, I'll add it.

Why are you removing this?

I don't see any purpose why it should exist for now.
I've added generic plugin manipulations (plugin get, show, enable, disable) so the WhoAmI plugin can be accessed here.
Another reason why I don't see the sense of its existence as a separate plugin, - this special list of plugins is really just for the plugins that require some deep configuration. We don't have the documentation for the plugin too.

Why are you removing this comment?

Actually, I removed it because of the thing I explained at the beginning here (about RDN).

Part of me wonders if this could actually be split to about 3 seperate patches (new plugin functionality, new cli functionality, new js integration). It's a pretty long patch ...

There will be a separate PR for Cockpit.
And the new plugin functionality is just 50-100 lines of code (and the rest is over the thousand).
I think it makes sense to keep it here because this new plugin functionality is really tightened up with the rest of PR content.

This name is odd. "refer-init" specificity the "init" part is not correct. Perhaps "refer-int"?

I am okay with it if nobody else doesn't mind.

I think this is enabling laziness, and I don't really like it. LDAP rdns are "based on the content of the entry", and by having it so that we can "have the rdn influcence the content of the entry" I think we are undoing some of that modeling.
I think I would ask what issue this is attempting to solve, because I think this change may be reflective of a problem elsewhere.

What I want to have is to be able to create an entry without specifying RDN in the parameters and take it from DN:
properties = {"specattrname": "acctPolicySubentry",
"limitattrname": "accountInactivityLimit"}
config = AccountPolicyConfig(inst, targetdn)
config.create(properties)

This behaviour already exsits. I think your code is doing the opposite. It's doing:

config AcctPol(inst, 'cn=something,....'
config.create(properties={....})

Then cn is inferred into properties.

But I think the way you changed the code will break static dn and dn-derivation from the properties.

There is already a mechanism for inferring the RDN from the properties, so why not go that way instead? IIRC you give the type a basedn, but no rdn, and then TDN is built from basedn + inferred rdn from properties.

As far as I understand, DN should always contain RDN which is part of the entry attributes.
We already generate DN out of RDN and Base DN. Why not to get RDN from DN? It will make some operations easier (like here, in CLI, when a user provides DN for the new entry)
Do I miss something?

I don't like the use of the word "edit" here because I was reserving that for an interactive entry editor I was plannig to add. Could this actually be something else?

I don't see any problem. I will change it to set word and it will still be accurate.

Thank you !!!!

Tests for the new dna plugin config types?

Sure, I'll add it.

Great!

Why are you removing this?

I don't see any purpose why it should exist for now.
I've added generic plugin manipulations (plugin get, show, enable, disable) so the WhoAmI plugin can be accessed here.
Another reason why I don't see the sense of its existence as a separate plugin, - this special list of plugins is really just for the plugins that require some deep configuration. We don't have the documentation for the plugin too.

Ahhh, sure. Just wanted to clarify.

Why are you removing this comment?

Actually, I removed it because of the thing I explained at the beginning here (about RDN).

Part of me wonders if this could actually be split to about 3 seperate patches (new plugin functionality, new cli functionality, new js integration). It's a pretty long patch ...

There will be a separate PR for Cockpit.
And the new plugin functionality is just 50-100 lines of code (and the rest is over the thousand).
I think it makes sense to keep it here because this new plugin functionality is really tightened up with the rest of PR content.

Sure,

1 new commit added

  • Port test for DNA plugin, change edit to set words, fix DN issues
5 years ago

This behaviour already exsits. I think your code is doing the opposite. It's doing:
config AcctPol(inst, 'cn=something,....'
config.create(properties={....})
Then cn is inferred into properties.
But I think the way you changed the code will break static dn and dn-derivation from the properties.

I don't see how it breaks static DN and DN-derivation... I checked some test suites and lib389 performs the same way after my previous change... But I moved the code to generic_object_add function so we can get rid of additional magic and make it more transparent.

There is already a mechanism for inferring the RDN from the properties, so why not go that way instead? IIRC you give the type a basedn, but no rdn, and then TDN is built from basedn + inferred rdn from properties.

I wanted exactly the oposite. I want to substruct the RDN from DN and put it to the properties.
Check what I've done in generic_object_add and then check how I use it for account-policy (and other plugins).

Tests for the new dna plugin config types?
Sure, I'll add it.

Great!

I've ported the only test we have for the DNA Shared Config. It is simple but it exposes basic functionality of the object. More deep multi-master topology testing will require much more time. So I think the current test is enough for the basic purpose (and we have one more test ported to suites+DSLdapObject - yay!).

Okay, better idea. I'm going to write and submit a "dn construction test" that asserts all the various ways we build dn's for objects today, and if that passes, we go ahead? That way we can assert everything works as we want :)

https://pagure.io/389-ds-base/pull-request/50267

So I have noticed something INTERESTING writing these tests. It looks like what you want todo (allow properties to be derived from the rdn value) already works provided the rdn=X and rdn: Y is in properties. It's actually a lib389 check that blocks the missing rdn in properties case, (commented in the test).

I think this is also a feature of 389-ds, it doesn't look like it's a lib389 behaviour because the logs clearly show we are only sending that single property.

So I'd be curious to see these tests run with your patch as well, and if it corrects that case.

The tests pass! Thank you!
Please, check once again because all of the comments were incorporated, I think.

Seems okay to me now I think, I think that the dn construction tests in 50267 may need to change after this patch though. Anyway ack from me. Any comments @mreynolds ?

rebased onto 46e28cb

5 years ago

Pull-Request has been merged by spichugi

5 years ago

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/3318

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago
Metadata
Changes Summary 30
+84
file added
dirsrvtests/tests/suites/plugins/dna_test.py
-122
file removed
dirsrvtests/tests/tickets/ticket47937_test.py
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+2 -2
file changed
src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+1 -1
file changed
src/cockpit/389-console/src/plugins.jsx
+5 -1
file changed
src/lib389/lib389/_mapped_object.py
+17 -6
file changed
src/lib389/lib389/cli_conf/__init__.py
+5 -5
file changed
src/lib389/lib389/cli_conf/plugin.py
+107 -5
file changed
src/lib389/lib389/cli_conf/plugins/accountpolicy.py
+111 -5
file changed
src/lib389/lib389/cli_conf/plugins/attruniq.py
+212 -151
file changed
src/lib389/lib389/cli_conf/plugins/automember.py
+235 -5
file changed
src/lib389/lib389/cli_conf/plugins/dna.py
+94 -3
file changed
src/lib389/lib389/cli_conf/plugins/linkedattr.py
+220 -5
file changed
src/lib389/lib389/cli_conf/plugins/managedentries.py
+53 -46
file changed
src/lib389/lib389/cli_conf/plugins/memberof.py
+74 -2
file changed
src/lib389/lib389/cli_conf/plugins/passthroughauth.py
+34 -174
file changed
src/lib389/lib389/cli_conf/plugins/referint.py
+38 -4
file changed
src/lib389/lib389/cli_conf/plugins/retrochangelog.py
+52 -213
file changed
src/lib389/lib389/cli_conf/plugins/rootdn_ac.py
+27 -12
file changed
src/lib389/lib389/cli_conf/plugins/usn.py
-16
file removed
src/lib389/lib389/cli_conf/plugins/whoami.py
+2 -2
file changed
src/lib389/lib389/cli_conf/pwpolicy.py
+205 -3
file changed
src/lib389/lib389/plugins.py