From 8d00d7c13038abc152afbd46c96108753506fb77 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: May 30 2012 06:46:21 +0000 Subject: Enforce sizelimit in permission-find, post_callback returns truncated We actually perform two searches in permission-find. The first looks for matches within the permission object itself. The second looks at matches in the underlying aci. We need to break out in two places. The first is if we find enough matches in the permission itself. The second when we are appending matches from acis. The post_callback() definition needed to be modified to return the truncated value so a plugin author can modify that value. https://fedorahosted.org/freeipa/ticket/2322 --- diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 58d53fd..93852a2 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -1857,9 +1857,9 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): for callback in self.POST_CALLBACKS: if hasattr(callback, 'im_self'): - callback(ldap, entries, truncated, *args, **options) + truncated = callback(ldap, entries, truncated, *args, **options) else: - callback(self, ldap, entries, truncated, *args, **options) + truncated = callback(self, ldap, entries, truncated, *args, **options) if self.sort_result_entries: if self.obj.primary_key: @@ -1884,7 +1884,7 @@ class LDAPSearch(BaseLDAPCommand, crud.Search): return (filters, base_dn, scope) def post_callback(self, ldap, entries, truncated, *args, **options): - pass + return truncated def exc_callback(self, args, options, exc, call_func, *call_args, **call_kwargs): raise exc diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index e26332d..e2b5995 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -132,7 +132,7 @@ EXAMPLES: SRV record: 0 3 389 fast.example.com, 0 1 389 slow.example.com, 1 1 389 backup.example.com - Modify SRV record '0 3 389 fast.example.com'? Yes/No (default No): + Modify SRV record '0 3 389 fast.example.com'? Yes/No (default No): Modify SRV record '0 1 389 slow.example.com'? Yes/No (default No): y SRV Priority [0]: (keep the default value) SRV Weight [1]: 2 (modified value) @@ -2589,6 +2589,8 @@ class dnsrecord_find(LDAPSearch): for entry in entries: self.obj.postprocess_record(entry[1], **options) + return truncated + api.register(dnsrecord_find) class dns_resolve(Command): diff --git a/ipalib/plugins/entitle.py b/ipalib/plugins/entitle.py index 6ade854..5ccdb65 100644 --- a/ipalib/plugins/entitle.py +++ b/ipalib/plugins/entitle.py @@ -461,6 +461,7 @@ class entitle_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if len(entries) == 0: raise errors.NotRegisteredError() + return truncated api.register(entitle_find) diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index 662cff3..96b73cc 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -769,7 +769,7 @@ class host_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if options.get('pkey_only', False): - return + return truncated for entry in entries: (dn, entry_attrs) = entry set_certificate_attrs(entry_attrs) @@ -785,6 +785,8 @@ class host_find(LDAPSearch): output_sshpubkey(ldap, dn, entry_attrs) + return truncated + api.register(host_find) diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py index 2a9a0a5..b68c458 100644 --- a/ipalib/plugins/hostgroup.py +++ b/ipalib/plugins/hostgroup.py @@ -182,10 +182,11 @@ class hostgroup_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if options.get('pkey_only', False): - return + return truncated for entry in entries: (dn, entry_attrs) = entry self.obj.suppress_netgroup_memberof(dn, entry_attrs) + return truncated api.register(hostgroup_find) diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py index 18fdcdd..a484ff6 100644 --- a/ipalib/plugins/permission.py +++ b/ipalib/plugins/permission.py @@ -351,7 +351,7 @@ class permission_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if options.pop('pkey_only', False): - return + return truncated for entry in entries: (dn, attrs) = entry try: @@ -363,6 +363,15 @@ class permission_find(LDAPSearch): attrs[attr] = aci[attr] except errors.NotFound: self.debug('ACI not found for %s' % attrs['cn'][0]) + if truncated: + # size/time limit met, no need to search acis + return truncated + + if 'sizelimit' in options: + max_entries = options['sizelimit'] + else: + config = ldap.get_ipa_config()[1] + max_entries = config['ipasearchrecordslimit'] # Now find all the ACIs that match. Once we find them, add any that # aren't already in the list along with their permission info. @@ -398,7 +407,12 @@ class permission_find(LDAPSearch): dn = permission['dn'] del permission['dn'] if (dn, permission) not in entries: - entries.append((dn, permission)) + if len(entries) < max_entries: + entries.append((dn, permission)) + else: + truncated = True + break + return truncated api.register(permission_find) diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py index 7be590f..77e6f2c 100644 --- a/ipalib/plugins/pwpolicy.py +++ b/ipalib/plugins/pwpolicy.py @@ -498,4 +498,6 @@ class pwpolicy_find(LDAPSearch): except KeyError: pass + return truncated + api.register(pwpolicy_find) diff --git a/ipalib/plugins/selinuxusermap.py b/ipalib/plugins/selinuxusermap.py index e6179ce..160e68c 100644 --- a/ipalib/plugins/selinuxusermap.py +++ b/ipalib/plugins/selinuxusermap.py @@ -318,10 +318,11 @@ all=True)['result'] def post_callback(self, ldap, entries, truncated, *args, **options): if options.get('pkey_only', False): - return + return truncated for entry in entries: (dn, attrs) = entry self.obj._convert_seealso(ldap, attrs, **options) + return truncated api.register(selinuxusermap_find) diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 333d5bb..24a0a0f 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -399,11 +399,12 @@ class service_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if options.get('pkey_only', False): - return + return truncated for entry in entries: (dn, entry_attrs) = entry self.obj.get_password_attributes(ldap, dn, entry_attrs) set_certificate_attrs(entry_attrs) + return truncated api.register(service_find) diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index 2e069bd..b48e680 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -625,13 +625,14 @@ class user_find(LDAPSearch): def post_callback(self, ldap, entries, truncated, *args, **options): if options.get('pkey_only', False): - return + return truncated for entry in entries: (dn, attrs) = entry self.obj._convert_manager(attrs, **options) self.obj.get_password_attributes(ldap, dn, attrs) convert_nsaccountlock(attrs) output_sshpubkey(ldap, dn, attrs) + return truncated msg_summary = ngettext( '%(count)d user matched', '%(count)d users matched', 0 diff --git a/tests/test_xmlrpc/test_permission_plugin.py b/tests/test_xmlrpc/test_permission_plugin.py index 28db7dc..d8ff149 100644 --- a/tests/test_xmlrpc/test_permission_plugin.py +++ b/tests/test_xmlrpc/test_permission_plugin.py @@ -387,6 +387,80 @@ class test_permission(Declarative): dict( + desc='Search for %r with a limit of 1 (truncated)' % permission1, + command=('permission_find', [permission1], dict(sizelimit=1)), + expected=dict( + count=1, + truncated=True, + summary=u'1 permission matched', + result=[ + { + 'dn': lambda x: DN(x) == permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + dict( + desc='Search for %r with a limit of 2' % permission1, + command=('permission_find', [permission1], dict(sizelimit=2)), + expected=dict( + count=2, + truncated=False, + summary=u'2 permissions matched', + result=[ + { + 'dn': lambda x: DN(x) == permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + { + 'dn': lambda x: DN(x) == permission2_dn, + 'cn': [permission2], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + # This tests setting truncated to True in the post_callback of + # permission_find(). The return order in LDAP is not guaranteed + # but in practice this is the first entry it finds. This is subject + # to change. + dict( + desc='Search for permissions by attr with a limit of 1 (truncated)', + command=('permission_find', [], dict(attrs=u'ipaenabledflag', + sizelimit=1)), + expected=dict( + count=1, + truncated=True, + summary=u'1 permission matched', + result=[ + { + 'dn': lambda x: DN(x) == DN(('cn', 'Modify HBAC rule'), + api.env.container_permission,api.env.basedn), + 'cn': [u'Modify HBAC rule'], + 'member_privilege': [u'HBAC Administrator'], + 'permissions' : [u'write'], + 'attrs': [u'servicecategory', u'sourcehostcategory', u'cn', u'description', u'ipaenabledflag', u'accesstime', u'usercategory', u'hostcategory', u'accessruletype', u'sourcehost'], + 'subtree' : u'ldap:///ipauniqueid=*,cn=hbac,%s' % api.env.basedn, + 'memberindirect': [u'cn=hbac administrator,cn=privileges,cn=pbac,%s' % api.env.basedn, u'cn=it security specialist,cn=roles,cn=accounts,%s' % api.env.basedn], + }, + ], + ), + ), + + + dict( desc='Update %r' % permission1, command=( 'permission_mod', [permission1], dict(permissions=u'read', memberof=u'ipausers')