From 1dd9e1407361bdd6ed337c70dcb1d209ce034cb6 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Jul 19 2011 11:06:16 +0000 Subject: Optionally wait for 389-ds postop plugins to complete Add a new command that lets you wait for an attribute to appear in a value. Using this you can do things like wait for a managed entry to be created, adding a new objectclass to the parent entry. This is controlled by a new booleon option, wait_for_attr, defaulting to False. https://fedorahosted.org/freeipa/ticket/1144 --- diff --git a/ipalib/constants.py b/ipalib/constants.py index 23e8025..026e073 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -148,6 +148,7 @@ DEFAULT_CONFIG = ( # Enable certain optional plugins: ('enable_ra', False), ('ra_plugin', 'selfsign'), + ('wait_for_attr', False), # Used when verifying that the API hasn't changed. Not for production. ('validate_api', False), diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 8d58422..1ff7a2a 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -194,6 +194,25 @@ def get_effective_rights(ldap, dn, attrs=None): return rdict +def entry_from_entry(entry, newentry): + """ + Python is more or less pass-by-value except for immutable objects. So if + you pass in a dict to a function you are free to change members of that + dict but you can't create a new dict in the function and expect to replace + what was passed in. + + In some post-op plugins that is exactly what we want to do, so here is a + clumsy way around the problem. + """ + + # Wipe out the current data + for e in entry.keys(): + del entry[e] + + # Re-populate it with new wentry + for e in newentry: + entry[e] = newentry[e] + def wait_for_memberof(keys, entry_start, completed, show_command, adding=True): """ When adding or removing reverse members we are faking an update to @@ -238,6 +257,40 @@ def wait_for_memberof(keys, entry_start, completed, show_command, adding=True): return entry_attrs +def wait_for_value(ldap, dn, attr, value): + """ + 389-ds postoperation plugins are executed after the data has been + returned to a client. This means that plugins that add data in a + postop are not included in data returned to the user. + + The downside of waiting is that this increases the time of the + command. + + The updated entry is returned. + """ + # Loop a few times to give the postop-plugin a chance to complete + # Don't sleep for more than 6 seconds. + x = 0 + while x < 20: + # sleep first because the first search, even on a quiet system, + # almost always fails. + time.sleep(.3) + x = x + 1 + + # FIXME: put a try/except around here? I think it is probably better + # to just let the exception filter up to the caller. + (dn, entry_attrs) = ldap.get_entry( dn, ['*']) + if attr in entry_attrs: + if isinstance(entry_attrs[attr], (list, tuple)): + values = map(lambda y:y.lower(), entry_attrs[attr]) + if value.lower() in values: + break + else: + if value.lower() == entry_attrs[attr].lower(): + break + + return entry_attrs + class LDAPObject(Object): """ Object representing a LDAP entry. diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py index 11ed663..ef44a8b 100644 --- a/ipalib/plugins/hostgroup.py +++ b/ipalib/plugins/hostgroup.py @@ -98,6 +98,14 @@ class hostgroup_add(LDAPCreate): msg_summary = _('Added hostgroup "%(value)s"') + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + if self.api.env.wait_for_attr: + newentry = wait_for_value(ldap, dn, 'objectclass', 'mepOriginEntry') + entry_from_entry(entry_attrs, newentry) + + return dn + + api.register(hostgroup_add) diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index e6060c0..3068c62 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -402,6 +402,11 @@ class user_add(LDAPCreate): self.api.Command['user_mod'](keys[-1], **kw) except (errors.EmptyModlist, errors.NotFound): pass + else: + if self.api.env.wait_for_attr: + newentry = wait_for_value(ldap, dn, 'objectclass', 'mepOriginEntry') + entry_from_entry(entry_attrs, newentry) + return dn api.register(user_add) diff --git a/tests/test_xmlrpc/objectclasses.py b/tests/test_xmlrpc/objectclasses.py index 41350f0..29cd6af 100644 --- a/tests/test_xmlrpc/objectclasses.py +++ b/tests/test_xmlrpc/objectclasses.py @@ -21,7 +21,7 @@ Defines the expected objectclass for various entries. """ -user = [ +user_base = [ u'top', u'person', u'organizationalperson', @@ -33,6 +33,8 @@ user = [ u'ipaobject', ] +user = user_base + [u'mepOriginEntry'] + group = [ u'top', u'groupofnames', diff --git a/tests/test_xmlrpc/test_attr.py b/tests/test_xmlrpc/test_attr.py index 9f1b1c5..1ea6a88 100644 --- a/tests/test_xmlrpc/test_attr.py +++ b/tests/test_xmlrpc/test_attr.py @@ -61,6 +61,9 @@ class test_attr(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, ), ), diff --git a/tests/test_xmlrpc/test_group_plugin.py b/tests/test_xmlrpc/test_group_plugin.py index 97349e8..d4f6cd4 100644 --- a/tests/test_xmlrpc/test_group_plugin.py +++ b/tests/test_xmlrpc/test_group_plugin.py @@ -590,6 +590,9 @@ class test_group(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user1, api.env.basedn), ), ), @@ -691,7 +694,7 @@ class test_group(Declarative): homedirectory=[u'/home/tuser1'], krbprincipalname=[u'tuser1@' + api.env.realm], loginshell=[u'/bin/sh'], - objectclass=objectclasses.user, + objectclass=objectclasses.user_base, sn=[u'User1'], uid=[user1], uidnumber=[fuzzy_digits], diff --git a/tests/test_xmlrpc/test_hostgroup_plugin.py b/tests/test_xmlrpc/test_hostgroup_plugin.py index 99ca922..803b8a7 100644 --- a/tests/test_xmlrpc/test_hostgroup_plugin.py +++ b/tests/test_xmlrpc/test_hostgroup_plugin.py @@ -23,6 +23,7 @@ Test the `ipalib.plugins.hostgroup` module. """ from ipalib import api, errors +from ipalib.dn import DN from tests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid from tests.test_xmlrpc import objectclasses @@ -79,6 +80,7 @@ class test_hostgroup(Declarative): objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], ipauniqueid=[fuzzy_uuid], + mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup1, api.env.basedn)], ), ), ), diff --git a/tests/test_xmlrpc/test_krbtpolicy.py b/tests/test_xmlrpc/test_krbtpolicy.py index bbfadb3..4d2443e 100644 --- a/tests/test_xmlrpc/test_krbtpolicy.py +++ b/tests/test_xmlrpc/test_krbtpolicy.py @@ -106,6 +106,9 @@ class test_krbtpolicy(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user1, api.env.basedn) ), ), diff --git a/tests/test_xmlrpc/test_nesting.py b/tests/test_xmlrpc/test_nesting.py index 5418628..0d1c785 100644 --- a/tests/test_xmlrpc/test_nesting.py +++ b/tests/test_xmlrpc/test_nesting.py @@ -167,6 +167,9 @@ class test_nesting(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user1, api.env.basedn) ), ), @@ -196,6 +199,9 @@ class test_nesting(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user2, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user2, api.env.basedn) ), ), @@ -225,6 +231,9 @@ class test_nesting(Declarative): cn=[u'Test User3'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user3, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user3, api.env.basedn) ), ), @@ -254,6 +263,9 @@ class test_nesting(Declarative): cn=[u'Test User4'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user4, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user4, api.env.basedn) ), ), @@ -623,6 +635,7 @@ class test_nesting(Declarative): objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[u'cn=%s,cn=ng,cn=alt,%s' % (hostgroup1, api.env.basedn)], ), ), ), @@ -642,6 +655,7 @@ class test_nesting(Declarative): objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 2'], ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[u'cn=%s,cn=ng,cn=alt,%s' % (hostgroup2, api.env.basedn)], ), ), ), diff --git a/tests/test_xmlrpc/test_netgroup_plugin.py b/tests/test_xmlrpc/test_netgroup_plugin.py index 47256e4..fcb3b06 100644 --- a/tests/test_xmlrpc/test_netgroup_plugin.py +++ b/tests/test_xmlrpc/test_netgroup_plugin.py @@ -183,6 +183,8 @@ class test_netgroup(Declarative): cn=[hostgroup1], objectclass=objectclasses.hostgroup, description=[u'Test hostgroup 1'], + mepmanagedentry=['cn=%s,cn=ng,cn=alt,%s' % (hostgroup1, api +.env.basedn)], ipauniqueid=[fuzzy_uuid], ), ), @@ -212,6 +214,9 @@ class test_netgroup(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user1, api.env.basedn), ), ), @@ -240,6 +245,9 @@ class test_netgroup(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user2, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=%s,cn=users,cn=accounts,%s' % (user2, api.env.basedn), ), ), diff --git a/tests/test_xmlrpc/test_replace.py b/tests/test_xmlrpc/test_replace.py index 6681778..4209e70 100644 --- a/tests/test_xmlrpc/test_replace.py +++ b/tests/test_xmlrpc/test_replace.py @@ -67,6 +67,9 @@ class test_replace(Declarative): initials=[u'TU'], mail=[u'test1@example.com', u'test2@example.com'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, ), ), diff --git a/tests/test_xmlrpc/test_user_plugin.py b/tests/test_xmlrpc/test_user_plugin.py index 36e19de..e0f2d7f 100644 --- a/tests/test_xmlrpc/test_user_plugin.py +++ b/tests/test_xmlrpc/test_user_plugin.py @@ -98,6 +98,9 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, ), ), @@ -153,7 +156,7 @@ class test_user(Declarative): 'krbprincipalname': [u'tuser1@' + api.env.realm], 'loginshell': [u'/bin/sh'], 'memberof_group': [u'ipausers'], - 'objectclass': objectclasses.user + [u'mepOriginEntry'], + 'objectclass': objectclasses.user, 'sn': [u'User1'], 'uid': [user1], 'uidnumber': [fuzzy_digits], @@ -442,6 +445,9 @@ class test_user(Declarative): cn=[u'Test User1'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, ), ), @@ -471,6 +477,9 @@ class test_user(Declarative): cn=[u'Test User2'], initials=[u'TU'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user2, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser2,cn=users,cn=accounts,' + api.env.basedn, ), ), @@ -602,6 +611,9 @@ class test_user(Declarative): postalcode=[u'01234-5678'], telephonenumber=[u'410-555-1212'], ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[u'cn=global_policy,cn=%s,cn=kerberos,%s' % (api.env.realm, api.env.basedn)], + mepmanagedentry=[u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn)], + memberof_group=[u'ipausers'], dn=u'uid=tuser1,cn=users,cn=accounts,' + api.env.basedn, ), ),