diff options
-rw-r--r-- | ipalib/plugins/range.py | 81 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_range_plugin.py | 151 |
2 files changed, 225 insertions, 7 deletions
diff --git a/ipalib/plugins/range.py b/ipalib/plugins/range.py index 95b00b39b..cc0c12753 100644 --- a/ipalib/plugins/range.py +++ b/ipalib/plugins/range.py @@ -91,6 +91,58 @@ class range(LDAPObject): if not options.get('all', False) or options.get('pkey_only', False): entry_attrs.pop('objectclass', None) + def check_ids_in_modified_range(self, old_base, old_size, new_base, new_size): + if new_base is None and new_size is None: + # nothing to check + return + if new_base is None: + new_base = old_base + if new_size is None: + new_size = old_size + old_interval = (old_base, old_base + old_size - 1) + new_interval = (new_base, new_base + new_size - 1) + checked_intervals = [] + low_diff = new_interval[0] - old_interval[0] + if low_diff > 0: + checked_intervals.append( + (old_interval[0], min(old_interval[1], new_interval[0] - 1))) + high_diff = old_interval[1] - new_interval[1] + if high_diff > 0: + checked_intervals.append( + (max(old_interval[0], new_interval[1] + 1), old_interval[1])) + + if not checked_intervals: + # range is equal or covers the entire old range, nothing to check + return + + ldap = self.backend + id_filter_base = ["(objectclass=posixAccount)", + "(objectclass=posixGroup)", + "(objectclass=ipaIDObject)"] + id_filter_ids = [] + + for id_low, id_high in checked_intervals: + id_filter_ids.append("(&(uidNumber>=%(low)d)(uidNumber<=%(high)d))" + % dict(low=id_low, high=id_high)) + id_filter_ids.append("(&(gidNumber>=%(low)d)(gidNumber<=%(high)d))" + % dict(low=id_low, high=id_high)) + id_filter = ldap.combine_filters( + [ldap.combine_filters(id_filter_base, "|"), + ldap.combine_filters(id_filter_ids, "|")], + "&") + + try: + (objects, truncated) = ldap.find_entries(filter=id_filter, + attrs_list=['uid', 'cn'], + base_dn=DN(api.env.container_accounts, api.env.basedn)) + except errors.NotFound: + # no objects in this range found, allow the command + pass + else: + raise errors.ValidationError(name="ipabaseid,ipaidrangesize", + error=_('range modification leaving objects with ID out ' + 'of the defined range is not allowed')) + class range_add(LDAPCreate): __doc__ = _('Add new ID range.') @@ -121,6 +173,18 @@ class range_del(LDAPDelete): msg_summary = _('Deleted ID range "%(value)s"') + def pre_callback(self, ldap, dn, *keys, **options): + try: + (old_dn, old_attrs) = ldap.get_entry(dn, ['ipabaseid', 'ipaidrangesize']) + except errors.NotFound: + self.obj.handle_not_found(*keys) + + old_base_id = int(old_attrs.get('ipabaseid', [0])[0]) + old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0]) + self.obj.check_ids_in_modified_range( + old_base_id, old_range_size, 0, 0) + return dn + class range_find(LDAPSearch): __doc__ = _('Search for ranges.') @@ -161,6 +225,23 @@ class range_mod(LDAPUpdate): def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) attrs_list.append('objectclass') + + try: + (old_dn, old_attrs) = ldap.get_entry(dn, ['ipabaseid', 'ipaidrangesize']) + except errors.NotFound: + self.obj.handle_not_found(*keys) + + old_base_id = int(old_attrs.get('ipabaseid', [0])[0]) + old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0]) + new_base_id = entry_attrs.get('ipabaseid') + if new_base_id is not None: + new_base_id = int(new_base_id) + new_range_size = entry_attrs.get('ipaidrangesize') + if new_range_size is not None: + new_range_size = int(new_range_size) + self.obj.check_ids_in_modified_range(old_base_id, old_range_size, + new_base_id, new_range_size) + return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): diff --git a/tests/test_xmlrpc/test_range_plugin.py b/tests/test_xmlrpc/test_range_plugin.py index ea4bc2b2d..4b7aa0893 100644 --- a/tests/test_xmlrpc/test_range_plugin.py +++ b/tests/test_xmlrpc/test_range_plugin.py @@ -23,21 +23,31 @@ Test the `ipalib/plugins/range.py` module, and XML-RPC in general. from ipalib import api, errors, _ from tests.util import assert_equal, Fuzzy -from xmlrpc_test import Declarative +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from tests.test_xmlrpc import objectclasses from ipapython.dn import * testrange1 = u't-range-1' +testrange1_base_id = 900000 +testrange1_size = 99999 + +user1=u'tuser1' +user1_uid = 900000 +group1=u'group1' +group1_gid = 900100 class test_range(Declarative): cleanup_commands = [ ('range_del', [testrange1], {}), + ('user_del', [user1], {}), + ('group_del', [group1], {}), ] tests = [ dict( desc='Create range %r' % (testrange1), command=('range_add', [testrange1], - dict(ipabaseid=900000, ipaidrangesize=99999, + dict(ipabaseid=testrange1_base_id, ipaidrangesize=testrange1_size, ipabaserid=1000, ipasecondarybaserid=20000)), expected=dict( result=dict( @@ -45,10 +55,10 @@ class test_range(Declarative): api.env.basedn), cn=[testrange1], objectclass=[u'ipaIDrange', u'ipadomainidrange'], - ipabaseid=[u'900000'], + ipabaseid=[unicode(testrange1_base_id)], ipabaserid=[u'1000'], ipasecondarybaserid=[u'20000'], - ipaidrangesize=[u'99999'], + ipaidrangesize=[unicode(testrange1_size)], iparangetype=[u'local domain range'], ), value=testrange1, @@ -64,10 +74,10 @@ class test_range(Declarative): dn=DN(('cn',testrange1),('cn','ranges'),('cn','etc'), api.env.basedn), cn=[testrange1], - ipabaseid=[u'900000'], + ipabaseid=[unicode(testrange1_base_id)], ipabaserid=[u'1000'], ipasecondarybaserid=[u'20000'], - ipaidrangesize=[u'99999'], + ipaidrangesize=[unicode(testrange1_size)], iparangetype=[u'local domain range'], ), value=testrange1, @@ -77,12 +87,97 @@ class test_range(Declarative): dict( + desc='Create user %r in range %r' % (user1, testrange1), + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + uidnumber=user1_uid) + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + krbprincipalname=[u'tuser1@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[unicode(user1_uid)], + gidnumber=[unicode(user1_uid)], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user1),('cn','groups'),('cn','accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), api.env.basedn) + ), + ), + ), + + + dict( + desc='Create group %r in range %r' % (group1, testrange1), + command=( + 'group_add', [group1], dict(description=u'Test desc 1', + gidnumber=group1_gid) + ), + expected=dict( + value=group1, + summary=u'Added group "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc 1'], + gidnumber=[unicode(group1_gid)], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn',group1),('cn','groups'),('cn','accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Try to modify range %r to get out bounds object #1' % (testrange1), + command=('range_mod', [testrange1], dict(ipabaseid=90001)), + expected=errors.ValidationError(name='ipabaseid,ipaidrangesize', + error=u'range modification leaving objects with ID out of the' + u' defined range is not allowed'), + ), + + + dict( + desc='Try to modify range %r to get out bounds object #2' % (testrange1), + command=('range_mod', [testrange1], dict(ipaidrangesize=100)), + expected=errors.ValidationError(name='ipabaseid,ipaidrangesize', + error=u'range modification leaving objects with ID out of the' + u' defined range is not allowed'), + ), + + + dict( + desc='Try to modify range %r to get out bounds object #3' % (testrange1), + command=('range_mod', [testrange1], dict(ipabaseid=100, ipaidrangesize=100)), + expected=errors.ValidationError(name='ipabaseid,ipaidrangesize', + error=u'range modification leaving objects with ID out of the' + u' defined range is not allowed'), + ), + + + dict( desc='Modify range %r' % (testrange1), command=('range_mod', [testrange1], dict(ipaidrangesize=90000)), expected=dict( result=dict( cn=[testrange1], - ipabaseid=[u'900000'], + ipabaseid=[unicode(testrange1_base_id)], ipabaserid=[u'1000'], ipasecondarybaserid=[u'20000'], ipaidrangesize=[u'90000'], @@ -93,4 +188,46 @@ class test_range(Declarative): ), ), + + dict( + desc='Try to delete range %r with active IDs inside it' % testrange1, + command=('range_del', [testrange1], {}), + expected=errors.ValidationError(name='ipabaseid,ipaidrangesize', + error=u'range modification leaving objects with ID out of the' + u' defined range is not allowed'), + ), + + + dict( + desc='Delete user %r' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + value=user1, + summary=u'Deleted user "%s"' % user1, + ), + ), + + + dict( + desc='Delete group %r' % group1, + command=('group_del', [group1], {}), + expected=dict( + result=dict(failed=u''), + value=group1, + summary=u'Deleted group "%s"' % group1, + ), + ), + + + dict( + desc='Delete range %r' % testrange1, + command=('range_del', [testrange1], {}), + expected=dict( + result=dict(failed=u''), + value=testrange1, + summary=u'Deleted ID range "%s"' % testrange1, + ), + ), + ] |