From 91a5d3349be3a8c6044684405a4e66f4ed1dd543 Mon Sep 17 00:00:00 2001 From: Ana Krivokapic Date: Jun 24 2013 12:30:06 +0000 Subject: Require rid-base and secondary-rid-base in idrange-add after ipa-adtrust-install Add a new API command 'adtrust_is_enabled', which can be used to determine whether ipa-adtrust-install has been run on the system. This new command is not visible in IPA CLI. Use this command in idrange_add to conditionally require rid-base and secondary-rid-base options. Add tests to cover the new functionality https://fedorahosted.org/freeipa/ticket/3634 --- diff --git a/API.txt b/API.txt index 1313460..bbffbd4 100644 --- a/API.txt +++ b/API.txt @@ -101,6 +101,10 @@ option: Str('version?', exclude='webui') output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('summary', (, ), None) output: Output('value', , None) +command: adtrust_is_enabled +args: 0,1,1 +option: Str('version?', exclude='webui') +output: Output('result', None, None) command: automember_add args: 1,7,3 arg: Str('cn', cli_name='automember_rule') diff --git a/VERSION b/VERSION index a95ccb9..c713b18 100644 --- a/VERSION +++ b/VERSION @@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=59 +IPA_API_VERSION_MINOR=60 diff --git a/ipalib/plugins/idrange.py b/ipalib/plugins/idrange.py index 54b835e..f258cbb 100644 --- a/ipalib/plugins/idrange.py +++ b/ipalib/plugins/idrange.py @@ -356,7 +356,7 @@ class idrange_add(LDAPCreate): may be given for a new ID range for the local domain while - --rid-bas + --rid-base --dom-sid must be given to add a new range for a trusted AD domain. @@ -381,6 +381,9 @@ class idrange_add(LDAPCreate): Also ensure that secondary-rid-base is prompted for when rid-base is specified and vice versa, in case that dom-sid was not specified. + + Also ensure that rid-base and secondary-rid-base is prompted for + if ipa-adtrust-install has been run on the system. """ # dom-sid can be specified using dom-sid or dom-name options @@ -410,6 +413,22 @@ class idrange_add(LDAPCreate): value = self.prompt_param(self.params['ipabaserid']) kw.update(dict(ipabaserid=value)) + # Prompt for rid-base and secondary-rid-base if ipa-adtrust-install + # has been run on the system + adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result'] + + if adtrust_is_enabled: + rid_base = kw.get('ipabaserid', None) + secondary_rid_base = kw.get('ipasecondarybaserid', None) + + if rid_base is None: + value = self.prompt_param(self.params['ipabaserid']) + kw.update(dict(ipabaserid=value)) + + if secondary_rid_base is None: + value = self.prompt_param(self.params['ipasecondarybaserid']) + kw.update(dict(ipasecondarybaserid=value)) + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) @@ -495,6 +514,20 @@ class idrange_add(LDAPCreate): error=_("Primary RID range and secondary RID range" " cannot overlap")) + # rid-base and secondary-rid-base must be set if + # ipa-adtrust-install has been run on the system + adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result'] + + if adtrust_is_enabled and not ( + is_set('ipabaserid') and is_set('ipasecondarybaserid')): + raise errors.ValidationError( + name='ID Range setup', + error=_( + 'You must specify both rid-base and ' + 'secondary-rid-base options, because ' + 'ipa-adtrust-install has already been run.' + ) + ) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py index 5c9360b..d2b5839 100644 --- a/ipalib/plugins/trust.py +++ b/ipalib/plugins/trust.py @@ -20,12 +20,9 @@ from ipalib.plugins.baseldap import * from ipalib.plugins.dns import dns_container_exists -from ipalib import api, Str, StrEnum, Password, DefaultFrom, _, ngettext, Object -from ipalib.parameters import Enum +from ipalib import api, Str, StrEnum, Password, _, ngettext from ipalib import Command from ipalib import errors -from ipapython import ipautil -from ipalib import util try: import pysss_murmur #pylint: disable=F0401 _murmur_installed = True @@ -843,3 +840,30 @@ class trust_resolve(Command): return dict(result=result) api.register(trust_resolve) + + +class adtrust_is_enabled(Command): + NO_CLI = True + + __doc__ = _('Determine whether ipa-adtrust-install has been run on this ' + 'system') + + def execute(self, *keys, **options): + ldap = self.api.Backend.ldap2 + adtrust_dn = DN( + ('cn', 'ADTRUST'), + ('cn', api.env.host), + ('cn', 'masters'), + ('cn', 'ipa'), + ('cn', 'etc'), + api.env.basedn + ) + + try: + ldap.get_entry(adtrust_dn) + except errors.NotFound: + return dict(result=False) + + return dict(result=True) + +api.register(adtrust_is_enabled) diff --git a/ipatests/test_cmdline/test_cli.py b/ipatests/test_cmdline/test_cli.py index fe411b7..fe5ffac 100644 --- a/ipatests/test_cmdline/test_cli.py +++ b/ipatests/test_cmdline/test_cli.py @@ -325,3 +325,77 @@ class TestCLIParsing(object): force=False, version=API_VERSION ) + + def test_idrange_add(self): + """ + Test idrange-add with interative prompt + """ + def test_with_interactive_input(): + with self.fake_stdin('5\n500000\n'): + self.check_command( + 'idrange_add range1 --base-id=1 --range-size=1', + 'idrange_add', + cn=u'range1', + ipabaseid=u'1', + ipaidrangesize=u'1', + ipabaserid=5, + ipasecondarybaserid=500000, + all=False, + raw=False, + version=API_VERSION + ) + + def test_with_command_line_options(): + self.check_command( + 'idrange_add range1 --base-id=1 --range-size=1 ' + '--rid-base=5 --secondary-rid-base=500000', + 'idrange_add', + cn=u'range1', + ipabaseid=u'1', + ipaidrangesize=u'1', + ipabaserid=u'5', + ipasecondarybaserid=u'500000', + all=False, + raw=False, + version=API_VERSION + ) + + def test_without_options(): + self.check_command( + 'idrange_add range1 --base-id=1 --range-size=1', + 'idrange_add', + cn=u'range1', + ipabaseid=u'1', + ipaidrangesize=u'1', + all=False, + raw=False, + version=API_VERSION + ) + + adtrust_dn = 'cn=ADTRUST,cn=%s,cn=masters,cn=ipa,cn=etc,%s' % \ + (api.env.host, api.env.basedn) + adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result'] + mockldap = None + + if not adtrust_is_enabled: + # ipa-adtrust-install not run - no need to pass rid-base + # and secondary-rid-base + test_without_options() + + # Create a mock service object to test against + adtrust_add = dict( + ipaconfigstring='enabledService', + objectclass=['top', 'nsContainer', 'ipaConfigObject'] + ) + + mockldap = util.MockLDAP() + mockldap.add_entry(adtrust_dn, adtrust_add) + + # Pass rid-base and secondary-rid-base interactively + test_with_interactive_input() + + # Pass rid-base and secondary-rid-base on the command-line + test_with_command_line_options() + + if not adtrust_is_enabled: + mockldap.del_entry(adtrust_dn) diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py index 3292d6a..df80e2f 100644 --- a/ipatests/test_xmlrpc/test_range_plugin.py +++ b/ipatests/test_xmlrpc/test_range_plugin.py @@ -21,13 +21,11 @@ Test the `ipalib/plugins/idrange.py` module, and XML-RPC in general. """ -from ipalib import api, errors, _ -from ipatests.util import assert_equal, Fuzzy -from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipalib import api, errors +from xmlrpc_test import Declarative, fuzzy_uuid from ipatests.test_xmlrpc import objectclasses -from ipapython.dn import * - -import ldap, ldap.sasl, ldap.modlist +from ipatests.util import MockLDAP +from ipapython.dn import DN id_shift = 0 rid_shift = 0 @@ -99,6 +97,7 @@ testrange8 = u'testrange8' testrange8_base_id = id_shift + 700 testrange8_size = 50 testrange8_base_rid = rid_shift + 700 +testrange8_secondary_base_rid = rid_shift + 800 testrange9 = u'testrange9' testrange9_base_id = id_shift + 800 @@ -155,59 +154,23 @@ group1_gid = id_shift + 900100 class test_range(Declarative): - - def __init__(self): - super(test_range, self).__init__() - self.connection = None - - @classmethod - def connect_ldap(self): - self.connection = ldap.initialize('ldap://{host}' - .format(host=api.env.host)) - - auth = ldap.sasl.gssapi("") - self.connection.sasl_interactive_bind_s('', auth) - - @classmethod - def add_entry(self, dn, mods): - ldif = ldap.modlist.addModlist(mods) - self.connection.add_s(dn, ldif) - @classmethod - def setUpClass(self): - super(test_range, self).setUpClass() - - self.tearDownClass() - - try: - self.connect_ldap() - - self.add_entry(testrange9_dn, testrange9_add) - self.add_entry(testrange10_dn, testrange10_add) - self.add_entry(testtrust_dn, testtrust_add) - - except ldap.ALREADY_EXISTS: - pass - - finally: - if self.connection is not None: - self.connection.unbind_s() + def setUpClass(cls): + super(test_range, cls).setUpClass() + cls.tearDownClass() + cls.mockldap = MockLDAP() + cls.mockldap.add_entry(testrange9_dn, testrange9_add) + cls.mockldap.add_entry(testrange10_dn, testrange10_add) + cls.mockldap.add_entry(testtrust_dn, testtrust_add) + cls.mockldap.unbind() @classmethod - def tearDownClass(self): - - try: - self.connect_ldap() - self.connection.delete_s(testrange9_dn) - self.connection.delete_s(testrange10_dn) - self.connection.delete_s(testtrust_dn) - - except ldap.NO_SUCH_OBJECT: - pass - - finally: - if self.connection is not None: - self.connection.unbind_s() + def tearDownClass(cls): + cls.mockldap = MockLDAP() + cls.mockldap.del_entry(testrange9_dn) + cls.mockldap.del_entry(testrange10_dn) + cls.mockldap.del_entry(testtrust_dn) + cls.mockldap.unbind() cleanup_commands = [ ('idrange_del', [testrange1, testrange2, testrange3, testrange4, @@ -508,7 +471,9 @@ class test_range(Declarative): desc='Create ID range %r' % (testrange8), command=('idrange_add', [testrange8], dict(ipabaseid=testrange8_base_id, - ipaidrangesize=testrange8_size)), + ipaidrangesize=testrange8_size, + ipabaserid=testrange8_base_rid, + ipasecondarybaserid=testrange8_secondary_base_rid)), expected=dict( result=dict( dn=DN(('cn',testrange8),('cn','ranges'),('cn','etc'), @@ -518,6 +483,8 @@ class test_range(Declarative): ipabaseid=[unicode(testrange8_base_id)], ipaidrangesize=[unicode(testrange8_size)], iparangetype=[u'local domain range'], + ipabaserid=[unicode(testrange8_base_rid)], + ipasecondarybaserid=[unicode(testrange8_secondary_base_rid)] ), value=testrange8, summary=u'Added ID range "%s"' % (testrange8), @@ -525,14 +492,6 @@ class test_range(Declarative): ), dict( - desc='Try to modify ID range %r so it has only primary rid range set' % (testrange8), - command=('idrange_mod', [testrange8], - dict(ipabaserid=testrange8_base_rid)), - expected=errors.ValidationError( - name='ID Range setup', error='Options secondary-rid-base and rid-base must be used together'), - ), - - dict( desc='Delete ID range %r' % testrange8, command=('idrange_del', [testrange8], {}), expected=dict( diff --git a/ipatests/util.py b/ipatests/util.py index 117d2c8..30fafdc 100644 --- a/ipatests/util.py +++ b/ipatests/util.py @@ -24,6 +24,9 @@ Common utility functions and classes for unit tests. import inspect import os from os import path +import ldap +import ldap.sasl +import ldap.modlist import tempfile import shutil import re @@ -32,6 +35,7 @@ from ipalib.plugable import Plugin from ipalib.request import context from ipapython.dn import DN + class TempDir(object): def __init__(self): self.__path = tempfile.mkdtemp(prefix='ipa.tests.') @@ -451,12 +455,6 @@ class ClassChecker(object): context.__dict__.clear() - - - - - - def check_TypeError(value, type_, name, callback, *args, **kw): """ Tests a standard TypeError raised with `errors.raise_TypeError`. @@ -635,3 +633,30 @@ class DummyClass(object): def _calledall(self): return self.__i == len(self.__calls) + + +class MockLDAP(object): + def __init__(self): + self.connection = ldap.initialize( + 'ldap://{host}'.format(host=ipalib.api.env.host) + ) + + auth = ldap.sasl.gssapi('') + self.connection.sasl_interactive_bind_s('', auth) + + def add_entry(self, dn, mods): + try: + ldif = ldap.modlist.addModlist(mods) + self.connection.add_s(dn, ldif) + except ldap.ALREADY_EXISTS: + pass + + def del_entry(self, dn): + try: + self.connection.delete_s(dn) + except ldap.NO_SUCH_OBJECT: + pass + + def unbind(self): + if self.connection is not None: + self.connection.unbind_s()