summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kosek <mkosek@redhat.com>2012-09-05 12:28:42 +0200
committerMartin Kosek <mkosek@redhat.com>2012-09-06 20:33:15 +0200
commit20e69716dbccb96f32c105a87c68138c4df57ef5 (patch)
tree82b2e67f7530a14add37a45d1fe0476a18121034
parentfd9ba3ed7cfb848d67f8237bfed7a7a3abbafc81 (diff)
downloadfreeipa-20e69716dbccb96f32c105a87c68138c4df57ef5.tar.gz
freeipa-20e69716dbccb96f32c105a87c68138c4df57ef5.tar.xz
freeipa-20e69716dbccb96f32c105a87c68138c4df57ef5.zip
Add range safety check for range_mod and range_del
range_mod and range_del command could easily create objects with ID which is suddenly out of specified range. This could cause issues in trust scenarios where range objects are used for computation of remote IDs. Add validator for both commands to check if there is any object with ID in the range which would become out-of-range as a pre_callback. Also add unit tests testing this new validator. https://fedorahosted.org/freeipa/ticket/2919
-rw-r--r--ipalib/plugins/range.py81
-rw-r--r--tests/test_xmlrpc/test_range_plugin.py151
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,
+ ),
+ ),
+
]