From 46e28cb4229f590c225f2a52bc8169e6fcc2d65b Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Mar 08 2019 17:49:19 +0000 Subject: Issue 50041 - Add CLI functionality for special plugins 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). Fix two small typos in pwpolicy CLI's arguments. Port test for DNA plugin. https://pagure.io/389-ds-base/issue/50041 Reviewed by: wibrown, mreynolds, mhonek (Thanks!) --- diff --git a/dirsrvtests/tests/suites/plugins/dna_test.py b/dirsrvtests/tests/suites/plugins/dna_test.py new file mode 100644 index 0000000..3418048 --- /dev/null +++ b/dirsrvtests/tests/suites/plugins/dna_test.py @@ -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') diff --git a/dirsrvtests/tests/tickets/ticket47937_test.py b/dirsrvtests/tests/tickets/ticket47937_test.py deleted file mode 100644 index 0a4c18d..0000000 --- a/dirsrvtests/tests/tickets/ticket47937_test.py +++ /dev/null @@ -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) diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx index 90ff501..fae8652 100644 --- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx +++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx @@ -13,7 +13,7 @@ class AccountPolicy extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx index 3d708de..0521a89 100644 --- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx +++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx @@ -13,7 +13,7 @@ class AttributeUniqueness extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx index 5982fcc..5216b15 100644 --- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx +++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx @@ -13,7 +13,7 @@ class LinkedAttributes extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx index 4bd5657..11771b7 100644 --- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx +++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx @@ -13,7 +13,7 @@ class ManagedEntries extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx index 51ecd59..d838054 100644 --- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx +++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx @@ -369,7 +369,7 @@ class MemberOf extends React.Component { } editConfig() { - this.cmdOperation("edit"); + this.cmdOperation("set"); } handleCheckboxChange(e) { @@ -473,7 +473,7 @@ class MemberOf extends React.Component { "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", "memberof", - "edit", + "set", "--scope", memberOfEntryScope || "delete", "--exclude", diff --git a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx index dfa08c7..5b6f76c 100644 --- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx +++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx @@ -13,7 +13,7 @@ class PassthroughAuthentication extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx index 20e97ff..96e8464 100644 --- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx +++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx @@ -13,7 +13,7 @@ class ReferentialIntegrity extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx index 51d7bb4..4e3490b 100644 --- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx +++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx @@ -13,7 +13,7 @@ class RetroChangelog extends React.Component { 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} diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx index 27c1d37..3e4d820 100644 --- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx +++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx @@ -13,7 +13,7 @@ class RootDNAccessControl extends React.Component { 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} diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx index d2b6932..5481d1a 100644 --- a/src/cockpit/389-console/src/plugins.jsx +++ b/src/cockpit/389-console/src/plugins.jsx @@ -196,7 +196,7 @@ export class Plugins extends React.Component { "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", "plugin", - "edit", + "set", data.name, "--type", data.type || "delete", diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index f79b344..f35b19e 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -705,7 +705,11 @@ class DSLdapObject(DSLogging): 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: diff --git a/src/lib389/lib389/cli_conf/__init__.py b/src/lib389/lib389/cli_conf/__init__.py index 836e05d..0ceb066 100644 --- a/src/lib389/lib389/cli_conf/__init__.py +++ b/src/lib389/lib389/cli_conf/__init__.py @@ -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 @@ def _args_to_attrs(args, arg_to_attr): 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): diff --git a/src/lib389/lib389/cli_conf/plugin.py b/src/lib389/lib389/cli_conf/plugin.py index 5cc7c8c..9509b84 100644 --- a/src/lib389/lib389/cli_conf/plugin.py +++ b/src/lib389/lib389/cli_conf/plugin.py @@ -17,7 +17,6 @@ from lib389.cli_conf import generic_object_edit 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 @@ arg_to_attr = { '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 @@ def create_parser(subparsers): 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 @@ def create_parser(subparsers): 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') diff --git a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py index d33e054..e2fa1e1 100644 --- a/src/lib389/lib389/cli_conf/plugins/accountpolicy.py +++ b/src/lib389/lib389/cli_conf/plugins/accountpolicy.py @@ -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') diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py index 4c04b05..17dac15 100644 --- a/src/lib389/lib389/cli_conf/plugins/attruniq.py +++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py b/src/lib389/lib389/cli_conf/plugins/automember.py index a4e757e..d9fe1dd 100644 --- a/src/lib389/lib389/cli_conf/plugins/automember.py +++ b/src/lib389/lib389/cli_conf/plugins/automember.py @@ -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.') diff --git a/src/lib389/lib389/cli_conf/plugins/dna.py b/src/lib389/lib389/cli_conf/plugins/dna.py index 50dd37f..08f66a4 100644 --- a/src/lib389/lib389/cli_conf/plugins/dna.py +++ b/src/lib389/lib389/cli_conf/plugins/dna.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/linkedattr.py b/src/lib389/lib389/cli_conf/plugins/linkedattr.py index 3f9a6ac..1a1c6ec 100644 --- a/src/lib389/lib389/cli_conf/plugins/linkedattr.py +++ b/src/lib389/lib389/cli_conf/plugins/linkedattr.py @@ -1,4 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2019 Red Hat, Inc. # Copyright (C) 2019 William Brown # 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 @@ def fixup(inst, basedn, log, args): 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 @@ def create_parser(subparsers): 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) diff --git a/src/lib389/lib389/cli_conf/plugins/managedentries.py b/src/lib389/lib389/cli_conf/plugins/managedentries.py index 18dca1b..cb5235b 100644 --- a/src/lib389/lib389/cli_conf/plugins/managedentries.py +++ b/src/lib389/lib389/cli_conf/plugins/managedentries.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/memberof.py b/src/lib389/lib389/cli_conf/plugins/memberof.py index 90fb774..fe54d02 100644 --- a/src/lib389/lib389/cli_conf/plugins/memberof.py +++ b/src/lib389/lib389/cli_conf/plugins/memberof.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2018 Red Hat, Inc. +# Copyright (C) 2019 Red Hat, Inc. # Copyright (C) 2019 William Brown # 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 @@ arg_to_attr = { 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 fixup(inst, basedn, log, args): 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.') diff --git a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py index ef6729e..616119a 100644 --- a/src/lib389/lib389/cli_conf/plugins/passthroughauth.py +++ b/src/lib389/lib389/cli_conf/plugins/passthroughauth.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/referint.py b/src/lib389/lib389/cli_conf/plugins/referint.py index bf4d07c..9482a14 100644 --- a/src/lib389/lib389/cli_conf/plugins/referint.py +++ b/src/lib389/lib389/cli_conf/plugins/referint.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py index 133d811..912c127 100644 --- a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py +++ b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py @@ -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) + + diff --git a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py index 7e1c017..63838a9 100644 --- a/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py +++ b/src/lib389/lib389/cli_conf/plugins/rootdn_ac.py @@ -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) diff --git a/src/lib389/lib389/cli_conf/plugins/usn.py b/src/lib389/lib389/cli_conf/plugins/usn.py index 59349fe..634ca7f 100644 --- a/src/lib389/lib389/cli_conf/plugins/usn.py +++ b/src/lib389/lib389/cli_conf/plugins/usn.py @@ -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 @@ def display_usn_mode(inst, basedn, log, args): 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)') diff --git a/src/lib389/lib389/cli_conf/plugins/whoami.py b/src/lib389/lib389/cli_conf/plugins/whoami.py deleted file mode 100644 index 2c3e62a..0000000 --- a/src/lib389/lib389/cli_conf/plugins/whoami.py +++ /dev/null @@ -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) diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py index 1413f30..b972aeb 100644 --- a/src/lib389/lib389/cli_conf/pwpolicy.py +++ b/src/lib389/lib389/cli_conf/pwpolicy.py @@ -199,8 +199,8 @@ def create_parser(subparsers): 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") diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py index c8b024b..18a2482 100644 --- a/src/lib389/lib389/plugins.py +++ b/src/lib389/lib389/plugins.py @@ -6,6 +6,7 @@ # See LICENSE for details. # --- END COPYRIGHT BLOCK --- +import collections import ldap import copy import os.path @@ -13,6 +14,7 @@ import os.path 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 @@ class AttributeUniquenessPlugin(Plugin): 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 @@ class MEPConfigs(DSLdapObjects): :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 @@ class MEPTemplate(DSLdapObject): 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 @@ class MEPTemplates(DSLdapObjects): 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 @@ class MemberOfSharedConfig(DSLdapObject): 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 @@ class PassThroughAuthenticationPlugin(Plugin): 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 @@ class DNAPluginConfigs(DSLdapObjects): 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