diff options
Diffstat (limited to 'ipatests/test_xmlrpc')
40 files changed, 20399 insertions, 0 deletions
diff --git a/ipatests/test_xmlrpc/__init__.py b/ipatests/test_xmlrpc/__init__.py new file mode 100644 index 000000000..1a8ecf1c2 --- /dev/null +++ b/ipatests/test_xmlrpc/__init__.py @@ -0,0 +1,22 @@ +# Authors: +# Jason Gerard DeRose <jderose@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Sub-package containing unit tests for `xmlrpc` package. +""" diff --git a/ipatests/test_xmlrpc/objectclasses.py b/ipatests/test_xmlrpc/objectclasses.py new file mode 100644 index 000000000..75ac3eb17 --- /dev/null +++ b/ipatests/test_xmlrpc/objectclasses.py @@ -0,0 +1,163 @@ +# Authors: +# Jason Gerard DeRose <jderose@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Defines the expected objectclass for various entries. +""" + +user_base = [ + u'top', + u'person', + u'organizationalperson', + u'inetorgperson', + u'inetuser', + u'posixaccount', + u'krbprincipalaux', + u'krbticketpolicyaux', + u'ipaobject', + u'ipasshuser', + u'ipaSshGroupOfPubKeys', +] + +user = user_base + [u'mepOriginEntry'] + +group = [ + u'top', + u'groupofnames', + u'nestedgroup', + u'ipausergroup', + u'ipaobject', +] + +externalgroup = group + [u'ipaexternalgroup'] +posixgroup = group + [u'posixgroup'] + +host = [ + u'ipasshhost', + u'ipaSshGroupOfPubKeys', + u'ieee802device', + u'ipaobject', + u'nshost', + u'ipahost', + u'pkiuser', + u'ipaservice', + u'krbprincipalaux', + u'krbprincipal', + u'top', +] + +hostgroup = [ + u'ipaobject', + u'ipahostgroup', + u'nestedGroup', + u'groupOfNames', + u'top', + u'mepOriginEntry', +] + +role = [ + u'groupofnames', + u'nestedgroup', + u'top', +] + +permission = [ + u'groupofnames', + u'ipapermission', + u'top' +] + +privilege = [ + u'nestedgroup', + u'groupofnames', + u'top' +] + +service = [ + u'krbprincipal', + u'krbprincipalaux', + u'krbticketpolicyaux', + u'ipaobject', + u'ipaservice', + u'pkiuser', + u'ipakrbprincipal', + u'top', +] + +hbacsvc = [ + u'ipaobject', + u'ipahbacservice', +] + +hbacsvcgroup = [ + u'ipaobject', + u'ipahbacservicegroup', + u'groupOfNames', + u'top', +] + +sudocmd = [ + u'ipaobject', + u'ipasudocmd', +] + +sudocmdgroup = [ + u'ipaobject', + u'ipasudocmdgrp', + u'groupOfNames', + u'top', +] + +netgroup = [ + u'ipaobject', + u'ipaassociation', + u'ipanisnetgroup', +] + +automember = [ + u'top', + u'automemberregexrule', +] + +selinuxusermap = [ + u'ipaassociation', + u'ipaselinuxusermap', +] + +hbacrule = [ + u'ipaassociation', + u'ipahbacrule', +] + +dnszone = [ + u'top', + u'idnsrecord', + u'idnszone', +] + +dnsrecord = [ + u'top', + u'idnsrecord', +] + +realmdomains = [ + u'top', + u'nsContainer', + u'domainRelatedObject', +] diff --git a/ipatests/test_xmlrpc/test_attr.py b/ipatests/test_xmlrpc/test_attr.py new file mode 100644 index 000000000..ef5b882c5 --- /dev/null +++ b/ipatests/test_xmlrpc/test_attr.py @@ -0,0 +1,562 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test --setattr and --addattr and other attribute-specific issues +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +user1=u'tuser1' + +class test_attr(Declarative): + + cleanup_commands = [ + ('user_del', [user1], {}), + ] + + tests = [ + + dict( + desc='Try to add user %r with single-value attribute set via ' + 'option and --addattr' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + addattr=u'sn=User2') + ), + expected=errors.OnlyOneValueAllowed(attr='sn'), + ), + + dict( + desc='Create %r' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + setattr=None) + ), + expected=dict( + value=user1, + summary=u'Added user "tuser1"', + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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'], + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Change givenname, add mail %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=(u'givenname=Finkle', u'mail=test@example.com')) + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Add another mail %r' % user1, + command=( + 'user_mod', [user1], dict(addattr=u'mail=test2@example.com') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Add two phone numbers at once %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=u'telephoneNumber=410-555-1212', addattr=u'telephoneNumber=301-555-1212') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'410-555-1212', u'301-555-1212'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Go from two phone numbers to one %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=u'telephoneNumber=301-555-1212') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'301-555-1212'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Add two more phone numbers %r' % user1, + command=( + 'user_mod', [user1], dict(addattr=(u'telephoneNumber=703-555-1212', u'telephoneNumber=202-888-9833')) + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'301-555-1212', u'202-888-9833', u'703-555-1212'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Delete one phone number for %r' % user1, + command=( + 'user_mod', [user1], dict(delattr=u'telephoneNumber=301-555-1212') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833', u'703-555-1212'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Try deleting the number again for %r' % user1, + command=( + 'user_mod', [user1], dict(delattr=u'telephoneNumber=301-555-1212') + ), + expected=errors.AttrValueNotFound(attr=u'telephonenumber', + value=u'301-555-1212') + ), + + + dict( + desc='Add and delete one phone number for %r' % user1, + command=( + 'user_mod', [user1], dict(addattr=u'telephoneNumber=301-555-1212', + delattr=u'telephoneNumber=202-888-9833') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'301-555-1212', u'703-555-1212'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Add and delete the same phone number for %r' % user1, + command=( + 'user_mod', [user1], dict(addattr=(u'telephoneNumber=301-555-1212', + u'telephoneNumber=202-888-9833'), + delattr=u'telephoneNumber=301-555-1212') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'703-555-1212', u'301-555-1212', u'202-888-9833'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Set and delete a phone number for %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=(u'telephoneNumber=301-555-1212', + u'telephoneNumber=202-888-9833'), + delattr=u'telephoneNumber=301-555-1212') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Try setting givenname to None with setattr in %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=(u'givenname=')) + ), + expected=errors.RequirementError(name='givenname'), + ), + + + dict( + desc='Try setting givenname to None with option in %r' % user1, + command=( + 'user_mod', [user1], dict(givenname=None) + ), + expected=errors.RequirementError(name='first'), + ), + + + dict( + desc='Make sure setting givenname works with option in %r' % user1, + command=( + 'user_mod', [user1], dict(givenname=u'Fred') + ), + expected=dict( + result=dict( + givenname=[u'Fred'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Make sure setting givenname works with setattr in %r' % user1, + command=( + 'user_mod', [user1], dict(setattr=u'givenname=Finkle') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + dict( + desc='Lock %r using setattr' % user1, + command=( + 'user_mod', [user1], dict(setattr=u'nsaccountlock=TrUe') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833'], + nsaccountlock=True, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + dict( + desc='Unlock %r using addattr&delattr' % user1, + command=( + 'user_mod', [user1], dict( + addattr=u'nsaccountlock=FaLsE', + delattr=u'nsaccountlock=TRUE') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test@example.com', u'test2@example.com'], + memberof_group=[u'ipausers'], + telephonenumber=[u'202-888-9833'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + dict( + desc='Try adding a new group search fields config entry', + command=( + 'config_mod', [], dict(addattr=u'ipagroupsearchfields=newattr') + ), + expected=errors.OnlyOneValueAllowed(attr='ipagroupsearchfields'), + ), + + dict( + desc='Try adding a new cert subject base config entry', + command=( + 'config_mod', [], dict(addattr=u'ipacertificatesubjectbase=0=DOMAIN.COM') + ), + expected=errors.ValidationError(name='ipacertificatesubjectbase', + error='attribute is not configurable'), + ), + + dict( + desc='Try deleting a required config entry', + command=( + 'config_mod', [], dict(delattr=u'ipasearchrecordslimit=100') + ), + expected=errors.RequirementError(name='ipasearchrecordslimit'), + ), + + dict( + desc='Try setting nonexistent attribute', + command=('config_mod', [], dict(setattr=u'invalid_attr=false')), + expected=errors.ObjectclassViolation( + info='attribute "invalid_attr" not allowed'), + ), + + dict( + desc='Try setting out-of-range krbpwdmaxfailure', + command=('pwpolicy_mod', [], dict(setattr=u'krbpwdmaxfailure=-1')), + expected=errors.ValidationError(name='krbpwdmaxfailure', + error='must be at least 0'), + ), + + dict( + desc='Try setting out-of-range maxfail', + command=('pwpolicy_mod', [], dict(krbpwdmaxfailure=u'-1')), + expected=errors.ValidationError(name='maxfail', + error='must be at least 0'), + ), + + dict( + desc='Try setting non-numeric krbpwdmaxfailure', + command=('pwpolicy_mod', [], dict(setattr=u'krbpwdmaxfailure=abc')), + expected=errors.ConversionError(name='krbpwdmaxfailure', + error='must be an integer'), + ), + + dict( + desc='Try setting non-numeric maxfail', + command=('pwpolicy_mod', [], dict(krbpwdmaxfailure=u'abc')), + expected=errors.ConversionError(name='maxfail', + error='must be an integer'), + ), + + dict( + desc='Try deleting bogus attribute', + command=('config_mod', [], dict(delattr=u'bogusattribute=xyz')), + expected=errors.ValidationError(name='bogusattribute', + error='No such attribute on this entry'), + ), + + dict( + desc='Try deleting empty attribute', + command=('config_mod', [], + dict(delattr=u'ipaCustomFields=See Also,seealso,false')), + expected=errors.ValidationError(name='ipacustomfields', + error='No such attribute on this entry'), + ), + + dict( + desc='Set and delete one value, plus try deleting a missing one', + command=('config_mod', [], dict( + delattr=[u'ipaCustomFields=See Also,seealso,false', + u'ipaCustomFields=Country,c,false'], + addattr=u'ipaCustomFields=See Also,seealso,false')), + expected=errors.AttrValueNotFound(attr='ipacustomfields', + value='Country,c,false'), + ), + + dict( + desc='Try to delete an operational attribute with --delattr', + command=('config_mod', [], dict( + delattr=u'creatorsName=cn=directory manager')), + expected=errors.DatabaseError( + desc='Server is unwilling to perform', info=''), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py new file mode 100644 index 000000000..a50860e66 --- /dev/null +++ b/ipatests/test_xmlrpc/test_automember_plugin.py @@ -0,0 +1,1095 @@ +# Authors: +# Jr Aquino <jr.aquino@citrix.com> +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/automember.py` module. +""" + +from ipalib import api, errors +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid + + +user1=u'tuser1' +manager1=u'mscott' +fqdn1 = u'web1.%s' % api.env.domain +short1 = u'web1' +fqdn2 = u'dev1.%s' % api.env.domain +short2 = u'dev1' +fqdn3 = u'web5.%s' % api.env.domain +short3 = u'web5' +fqdn4 = u'www5.%s' % api.env.domain +short4 = u'www5' +fqdn5 = u'webserver5.%s' % api.env.domain +short5 = u'webserver5' + +group1=u'group1' +defaultgroup1=u'defaultgroup1' +hostgroup1=u'hostgroup1' +hostgroup2=u'hostgroup2' +hostgroup3=u'hostgroup3' +hostgroup4=u'hostgroup4' +defaulthostgroup1=u'defaulthostgroup1' + +group_include_regex = u'mscott' +hostgroup_include_regex = u'^web[1-9]' +hostgroup_include_regex2 = u'^www[1-9]' +hostgroup_include_regex3 = u'webserver[1-9]' +hostgroup_exclude_regex = u'^web5' +hostgroup_exclude_regex2 = u'^www5' +hostgroup_exclude_regex3 = u'^webserver5' + + +class test_automember(Declarative): + + cleanup_commands = [ + ('user_del', [user1, manager1], {}), + ('group_del', [group1, defaultgroup1], {}), + ('host_del', [fqdn1, fqdn2, fqdn3, fqdn4, fqdn5], {}), + ('hostgroup_del', [hostgroup1, hostgroup2, hostgroup3, hostgroup4, defaulthostgroup1], {}), + ('automember_del', [group1], {'type': u'group'}), + ('automember_del', [hostgroup1], {'type': u'hostgroup'}), + ('automember_del', [hostgroup2], {'type': u'hostgroup'}), + ('automember_del', [hostgroup3], {'type': u'hostgroup'}), + ('automember_del', [hostgroup4], {'type': u'hostgroup'}), + ('automember_default_group_remove', [], {'type': u'hostgroup'}), + ('automember_default_group_remove', [], {'type': u'group'}), + + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent group rule %r' % group1, + command=('automember_add', [group1], + dict(description=u'Test desc', type=u'group')), + expected=errors.NotFound(reason=u'Group: %s not found!' % group1), + ), + + dict( + desc='Try to update non-existent group rule %r' % group1, + command=('automember_add', [group1], dict(type=u'group')), + expected=errors.NotFound(reason=u'Group: %s not found!' % group1), + ), + + dict( + desc='Try to delete non-existent group rule %r' % group1, + command=('automember_del', [group1], dict(type=u'group')), + expected=errors.NotFound(reason=u': auto_member_rule not found'), + ), + + + dict( + desc='Try to retrieve non-existent hostgroup rule %r' % hostgroup1, + command=('automember_add', [hostgroup1], + dict(description=u'Test desc', type=u'hostgroup')), + expected=errors.NotFound( + reason=u'Group: %s not found!' % hostgroup1), + ), + + dict( + desc='Try to update non-existent hostgroup rule %r' % hostgroup1, + command=('automember_add', [hostgroup1], dict(type=u'hostgroup')), + expected=errors.NotFound( + reason=u'Group: %s not found!' % hostgroup1), + ), + + dict( + desc='Try to delete non-existent hostgroup rule %r' % hostgroup1, + command=('automember_del', [hostgroup1], dict(type=u'hostgroup')), + expected=errors.NotFound(reason=u': auto_member_rule not found'), + ), + + + + dict( + desc='Create %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc') + ), + expected=dict( + value=group1, + summary=u'Added group "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup1, + command=( + 'hostgroup_add', [hostgroup1], dict(description=u'Test desc') + ), + expected=dict( + value=hostgroup1, + summary=u'Added hostgroup "%s"' % hostgroup1, + result=dict( + cn=[hostgroup1], + description=[u'Test desc'], + objectclass=objectclasses.hostgroup, + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn', hostgroup1), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup2, + command=( + 'hostgroup_add', [hostgroup2], dict(description=u'Test desc') + ), + expected=dict( + value=hostgroup2, + summary=u'Added hostgroup "%s"' % hostgroup2, + result=dict( + cn=[hostgroup2], + description=[u'Test desc'], + objectclass=objectclasses.hostgroup, + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn', hostgroup2), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup3, + command=( + 'hostgroup_add', [hostgroup3], dict(description=u'Test desc') + ), + expected=dict( + value=hostgroup3, + summary=u'Added hostgroup "%s"' % hostgroup3, + result=dict( + cn=[hostgroup3], + description=[u'Test desc'], + objectclass=objectclasses.hostgroup, + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn', hostgroup3), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup4, + command=( + 'hostgroup_add', [hostgroup4], dict(description=u'Test desc') + ), + expected=dict( + value=hostgroup4, + summary=u'Added hostgroup "%s"' % hostgroup4, + result=dict( + cn=[hostgroup4], + description=[u'Test desc'], + objectclass=objectclasses.hostgroup, + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn', hostgroup4), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % defaultgroup1, + command=( + 'group_add', [defaultgroup1], dict(description=u'Default test desc') + ), + expected=dict( + value=defaultgroup1, + summary=u'Added group "%s"' % defaultgroup1, + result=dict( + cn=[defaultgroup1], + description=[u'Default test desc'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % defaulthostgroup1, + command=( + 'hostgroup_add', [defaulthostgroup1], dict(description=u'Default test desc') + ), + expected=dict( + value=defaulthostgroup1, + summary=u'Added hostgroup "%s"' % defaulthostgroup1, + result=dict( + cn=[defaulthostgroup1], + description=[u'Default test desc'], + objectclass=objectclasses.hostgroup, + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn', defaulthostgroup1), ('cn', 'ng'), ('cn', 'alt'), api.env.basedn)], + dn=DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember %r' % group1, + command=( + 'automember_add', [group1], dict(description=u'Test desc', type=u'group') + ), + expected=dict( + value=group1, + summary=u'Added automember rule "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + objectclass=objectclasses.automember, + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember condition %r' % group1, + command=( + 'automember_add_condition', [group1], dict( + key=u'manager', type=u'group', + automemberinclusiveregex=[group_include_regex], + ) + ), + expected=dict( + value=group1, + summary=u'Added condition(s) to "%s"' % group1, + completed=1, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[group1], + description=[u'Test desc'], + automemberinclusiveregex=[u'manager=%s' % group_include_regex], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + ), + ), + ), + + + dict( + desc='Create automember %r' % hostgroup1, + command=( + 'automember_add', [hostgroup1], dict( + description=u'Test desc', type=u'hostgroup', + ) + ), + expected=dict( + value=hostgroup1, + summary=u'Added automember rule "%s"' % hostgroup1, + result=dict( + cn=[hostgroup1], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + objectclass=objectclasses.automember, + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember condition %r' % hostgroup1, + command=( + 'automember_add_condition', [hostgroup1], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_include_regex], + ) + ), + expected=dict( + value=hostgroup1, + summary=u'Added condition(s) to "%s"' % hostgroup1, + completed=1, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[hostgroup1], + description=[u'Test desc'], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + ), + ), + + + dict( + desc='Create duplicate automember condition %r' % hostgroup1, + command=( + 'automember_add_condition', [hostgroup1], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_include_regex], + ) + ), + expected=dict( + value=hostgroup1, + summary=u'Added condition(s) to "%s"' % hostgroup1, + completed=0, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex], + ), + ), + ), + + + dict( + desc='Create additional automember conditions %r' % hostgroup1, + command=( + 'automember_add_condition', [hostgroup1], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_include_regex2, hostgroup_include_regex3], + automemberexclusiveregex=[hostgroup_exclude_regex, hostgroup_exclude_regex2, hostgroup_exclude_regex3], + ) + ), + expected=dict( + value=hostgroup1, + summary=u'Added condition(s) to "%s"' % hostgroup1, + completed=5, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[hostgroup1], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, + u'fqdn=%s' % hostgroup_include_regex3, + u'fqdn=%s' % hostgroup_include_regex2, + ], + automemberexclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2, + u'fqdn=%s' % hostgroup_exclude_regex3, + u'fqdn=%s' % hostgroup_exclude_regex, + ], + ), + ), + ), + + + dict( + desc='Create automember %r' % hostgroup2, + command=( + 'automember_add', [hostgroup2], dict( + description=u'Test desc', type=u'hostgroup', + ) + ), + expected=dict( + value=hostgroup2, + summary=u'Added automember rule "%s"' % hostgroup2, + result=dict( + cn=[hostgroup2], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + objectclass=objectclasses.automember, + dn=DN(('cn', hostgroup2), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember condition %r' % hostgroup2, + command=( + 'automember_add_condition', [hostgroup2], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_exclude_regex], + ) + ), + expected=dict( + value=hostgroup2, + summary=u'Added condition(s) to "%s"' % hostgroup2, + completed=1, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[hostgroup2], + description=[u'Test desc'], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex], + automembertargetgroup=[DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + ), + ), + + + dict( + desc='Create automember %r' % hostgroup3, + command=( + 'automember_add', [hostgroup3], dict( + description=u'Test desc', type=u'hostgroup', + ) + ), + expected=dict( + value=hostgroup3, + summary=u'Added automember rule "%s"' % hostgroup3, + result=dict( + cn=[hostgroup3], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + objectclass=objectclasses.automember, + dn=DN(('cn', hostgroup3), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember condition %r' % hostgroup3, + command=( + 'automember_add_condition', [hostgroup3], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_exclude_regex2], + ) + ), + expected=dict( + value=hostgroup3, + summary=u'Added condition(s) to "%s"' % hostgroup3, + completed=1, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[hostgroup3], + description=[u'Test desc'], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2], + automembertargetgroup=[DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + ), + ), + + + dict( + desc='Create automember %r' % hostgroup4, + command=( + 'automember_add', [hostgroup4], dict( + description=u'Test desc', type=u'hostgroup', + ) + ), + expected=dict( + value=hostgroup4, + summary=u'Added automember rule "%s"' % hostgroup4, + result=dict( + cn=[hostgroup4], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + objectclass=objectclasses.automember, + dn=DN(('cn', hostgroup4), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ), + ), + + + dict( + desc='Create automember condition %r' % hostgroup4, + command=( + 'automember_add_condition', [hostgroup4], dict( + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_exclude_regex3], + ) + ), + expected=dict( + value=hostgroup4, + summary=u'Added condition(s) to "%s"' % hostgroup4, + completed=1, + failed=dict( + failed = dict( + automemberinclusiveregex=tuple(), + automemberexclusiveregex=tuple(), + ) + ), + result=dict( + cn=[hostgroup4], + description=[u'Test desc'], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex3], + automembertargetgroup=[DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + ), + ), + + + dict( + desc="Retrieve automember rule for group %s" % group1, + command=('automember_show', [group1], dict( + type=u'group', + ) + ), + expected=dict( + value=group1, + result=dict( + cn=[group1], + description=[u'Test desc'], + automemberinclusiveregex=[u'manager=%s' % group_include_regex], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % group1, + command=('automember_find', [group1], dict( + type=u'group' + ) + ), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + cn=[group1], + description=[u'Test desc'], + automemberinclusiveregex=[u'manager=%s' % group_include_regex], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + dn=DN(('cn', group1), ('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ], + summary=u'1 rules matched', + ), + ), + + + dict( + desc='Updated automember rule %r' % group1, + command=( + 'automember_mod', [group1], dict( + type=u'group', + description=u'New desc 1', + ) + ), + expected=dict( + result=dict( + cn=[group1], + description=[u'New desc 1'], + automemberinclusiveregex=[u'manager=%s' % group_include_regex], + automembertargetgroup=[DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + ), + summary=u'Modified automember rule "%s"' % group1, + value=group1, + ), + ), + + + dict( + desc="Retrieve automember rule for hostgroup %s" % hostgroup1, + command=('automember_show', [hostgroup1], dict( + type=u'hostgroup', + ) + ), + expected=dict( + value=hostgroup1, + result=dict( + cn=[hostgroup1], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, + u'fqdn=%s' % hostgroup_include_regex3, + u'fqdn=%s' % hostgroup_include_regex2, + ], + automemberexclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2, + u'fqdn=%s' % hostgroup_exclude_regex3, + u'fqdn=%s' % hostgroup_exclude_regex, + ], + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % hostgroup1, + command=('automember_find', [hostgroup1], dict( + type=u'hostgroup' + ) + ), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + cn=[hostgroup1], + description=[u'Test desc'], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, + u'fqdn=%s' % hostgroup_include_regex3, + u'fqdn=%s' % hostgroup_include_regex2, + ], + automemberexclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2, + u'fqdn=%s' % hostgroup_exclude_regex3, + u'fqdn=%s' % hostgroup_exclude_regex, + ], + dn=DN(('cn', hostgroup1), ('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + ), + ], + summary=u'1 rules matched', + ), + ), + + + dict( + desc='Updated automember rule %r' % hostgroup1, + command=( + 'automember_mod', [hostgroup1], dict( + type=u'hostgroup', + description=u'New desc 1', + ) + ), + expected=dict( + result=dict( + cn=[hostgroup1], + description=[u'New desc 1'], + automembertargetgroup=[DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + automemberinclusiveregex=[u'fqdn=%s' % hostgroup_include_regex, + u'fqdn=%s' % hostgroup_include_regex3, + u'fqdn=%s' % hostgroup_include_regex2, + ], + automemberexclusiveregex=[u'fqdn=%s' % hostgroup_exclude_regex2, + u'fqdn=%s' % hostgroup_exclude_regex3, + u'fqdn=%s' % hostgroup_exclude_regex, + ], + ), + summary=u'Modified automember rule "%s"' % hostgroup1, + value=hostgroup1, + ), + ), + + + dict( + desc='Set default automember group for groups', + command=( + 'automember_default_group_set', [], dict( + type=u'group', + automemberdefaultgroup=defaultgroup1 + ) + ), + expected=dict( + result=dict( + cn=[u'Group'], + automemberdefaultgroup=[DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + ), + value=u'group', + summary=u'Set default (fallback) group for automember "group"', + ), + ), + + + dict( + desc='Retrieve default automember group for groups', + command=( + 'automember_default_group_show', [], dict(type=u'group') + ), + expected=dict( + result=dict( + dn=DN(('cn', 'group'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + cn=[u'Group'], + automemberdefaultgroup=[DN(('cn', defaultgroup1), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + ), + value=u'group', + summary=None, + ), + ), + + + dict( + desc='Set default (fallback) automember group for hostgroups', + command=( + 'automember_default_group_set', [], dict( + type=u'hostgroup', + automemberdefaultgroup=defaulthostgroup1, + ) + ), + expected=dict( + result=dict( + cn=[u'Hostgroup'], + automemberdefaultgroup=[DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + value=u'hostgroup', + summary=u'Set default (fallback) group for automember "hostgroup"', + ), + ), + + + dict( + desc='Retrieve default automember group for hostgroups', + command=( + 'automember_default_group_show', [], dict( + type=u'hostgroup', + ) + ), + expected=dict( + result=dict( + dn=DN(('cn', 'hostgroup'), ('cn', 'automember'), ('cn', 'etc'), api.env.basedn), + cn=[u'Hostgroup'], + automemberdefaultgroup=[DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn)], + ), + value=u'hostgroup', + summary=None, + ), + ), + + + dict( + desc='Create %r' % manager1, + command=( + 'user_add', [manager1], dict(givenname=u'Michael', sn=u'Scott') + ), + expected=dict( + value=manager1, + summary=u'Added user "mscott"', + result=dict( + gecos=[u'Michael Scott'], + givenname=[u'Michael'], + homedirectory=[u'/home/mscott'], + krbprincipalname=[u'mscott@' + api.env.realm], + has_keytab=False, + has_password=False, + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'Scott'], + uid=[manager1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (manager1, api.env.domain)], + displayname=[u'Michael Scott'], + cn=[u'Michael Scott'], + initials=[u'MS'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), + api.env.basedn)], + mepmanagedentry=[DN(('cn', manager1), ('cn', 'groups'), ('cn', 'accounts'), + api.env.basedn)], + memberof_group=[u'defaultgroup1', u'ipausers'], + dn=DN(('uid', 'mscott'), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', manager=manager1) + ), + expected=dict( + value=user1, + summary=u'Added user "tuser1"', + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + krbprincipalname=[u'tuser1@' + api.env.realm], + has_keytab=False, + has_password=False, + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + manager=[DN(('uid', 'mscott'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn)], + 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'group1', u'ipausers'], + dn=DN(('uid', 'tuser1'), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=DN(('fqdn', fqdn1), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False, + has_password=False, + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + memberof_hostgroup=[hostgroup1], + memberofindirect_netgroup=[hostgroup1], + ), + ), + ), + + + dict( + desc='Create %r' % fqdn2, + command=('host_add', [fqdn2], + dict( + description=u'Test host 2', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn2, + summary=u'Added host "%s"' % fqdn2, + result=dict( + dn=DN(('fqdn', fqdn2), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), + fqdn=[fqdn2], + description=[u'Test host 2'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)], + has_keytab=False, + has_password=False, + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn2], + memberof_hostgroup=[defaulthostgroup1], + memberofindirect_netgroup=[defaulthostgroup1], + ), + ), + ), + + + dict( + desc='Create %r' % fqdn3, + command=('host_add', [fqdn3], + dict( + description=u'Test host 3', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn3, + summary=u'Added host "%s"' % fqdn3, + result=dict( + dn=DN(('fqdn', fqdn3), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), + fqdn=[fqdn3], + description=[u'Test host 3'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + has_keytab=False, + has_password=False, + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn3], + memberof_hostgroup=[hostgroup2], + memberofindirect_netgroup=[hostgroup2], + ), + ), + ), + + + dict( + desc='Create %r' % fqdn4, + command=('host_add', [fqdn4], + dict( + description=u'Test host 4', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn4, + summary=u'Added host "%s"' % fqdn4, + result=dict( + dn=DN(('fqdn', fqdn4), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), + fqdn=[fqdn4], + description=[u'Test host 4'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)], + has_keytab=False, + has_password=False, + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn4], + memberof_hostgroup=[hostgroup3], + memberofindirect_netgroup=[hostgroup3], + ), + ), + ), + + + dict( + desc='Create %r' % fqdn5, + command=('host_add', [fqdn5], + dict( + description=u'Test host 5', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn5, + summary=u'Added host "%s"' % fqdn5, + result=dict( + dn=DN(('fqdn', fqdn5), ('cn', 'computers'), ('cn', 'accounts'), api.env.basedn), + fqdn=[fqdn5], + description=[u'Test host 5'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn5, api.env.realm)], + has_keytab=False, + has_password=False, + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn5], + memberof_hostgroup=[hostgroup4], + memberofindirect_netgroup=[hostgroup4], + ), + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup1, + command=('hostgroup_show', [hostgroup1], {}), + expected=dict( + value=hostgroup1, + summary=None, + result={ + 'dn': DN(('cn', hostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + 'member_host': [u'%s' % fqdn1], + 'cn': [hostgroup1], + 'description': [u'Test desc'], + }, + ), + ), + + + dict( + desc='Retrieve %r' % defaulthostgroup1, + command=('hostgroup_show', [defaulthostgroup1], {}), + expected=dict( + value=defaulthostgroup1, + summary=None, + result={ + 'dn': DN(('cn', defaulthostgroup1), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + 'member_host': [u'%s' % fqdn2], + 'cn': [defaulthostgroup1], + 'description': [u'Default test desc'], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup2, + command=('hostgroup_show', [hostgroup2], {}), + expected=dict( + value=hostgroup2, + summary=None, + result={ + 'dn': DN(('cn', hostgroup2), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + 'member_host': [u'%s' % fqdn3], + 'cn': [hostgroup2], + 'description': [u'Test desc'], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup3, + command=('hostgroup_show', [hostgroup3], {}), + expected=dict( + value=hostgroup3, + summary=None, + result={ + 'dn': DN(('cn', hostgroup3), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + 'member_host': [u'%s' % fqdn4], + 'cn': [hostgroup3], + 'description': [u'Test desc'], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup4, + command=('hostgroup_show', [hostgroup4], {}), + expected=dict( + value=hostgroup4, + summary=None, + result={ + 'dn': DN(('cn', hostgroup4), ('cn', 'hostgroups'), ('cn', 'accounts'), api.env.basedn), + 'member_host': [u'%s' % fqdn5], + 'cn': [hostgroup4], + 'description': [u'Test desc'], + }, + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_automount_plugin.py b/ipatests/test_xmlrpc/test_automount_plugin.py new file mode 100644 index 000000000..e1af651c8 --- /dev/null +++ b/ipatests/test_xmlrpc/test_automount_plugin.py @@ -0,0 +1,582 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/automount.py' module. +""" + +import sys +import textwrap +import tempfile +import shutil + +from ipalib import api +from ipalib import errors +from ipapython.dn import DN + +from nose.tools import raises, assert_raises # pylint: disable=E0611 +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipatests.util import assert_deepequal + + +class MockTextui(list): + """Collects output lines""" + # Extend the mock object if other textui methods are called + def print_plain(self, line): + self.append(unicode(line)) + + +class AutomountTest(XMLRPC_test): + """Provides common functionality for automount tests""" + def check_tofiles(self): + """Check automountlocation_tofiles output against self.tofiles_output + """ + res = api.Command['automountlocation_tofiles'](self.locname) + + mock_ui = MockTextui() + command = api.Command['automountlocation_tofiles'] + command.output_for_cli(mock_ui, res, self.locname) + expected_output = self.tofiles_output + assert_deepequal(expected_output, u'\n'.join(mock_ui)) + + def check_import_roundtrip(self): + """Check automountlocation_tofiles/automountlocation_import roundtrip + + Loads self.tofiles_output (which should correspond to + automountlocation_tofiles output), then checks the resulting map + against tofiles_output again. + Do not use this if the test creates maps that aren't connected to + auto.master -- these can't be imported successfully. + """ + conf_directory = tempfile.mkdtemp() + + # Parse the tofiles_output into individual files, replace /etc/ by + # our temporary directory name + current_file = None + for line in self.tofiles_output.splitlines(): + line = line.replace('/etc/', '%s/' % conf_directory) + if line.startswith(conf_directory) and line.endswith(':'): + current_file = open(line.rstrip(':'), 'w') + elif '--------' in line: + current_file.close() + elif line.startswith('maps not connected to '): + break + else: + current_file.write(line + '\n') + current_file.close() + + self.failsafe_add(api.Object.automountlocation, self.locname) + + try: + # Feed the files to automountlocation_import & check + master_file = u'%s/auto.master' % conf_directory + automountlocation_import = api.Command['automountlocation_import'] + res = automountlocation_import(self.locname, master_file) + assert_deepequal(dict( + result=dict( + keys=lambda k: k, + maps=lambda m: m, + skipped=(), + duplicatemaps=(), + duplicatekeys=(), + )), res) + self.check_tofiles() + finally: + res = api.Command['automountlocation_del'](self.locname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Success; delete the temporary directory + shutil.rmtree(conf_directory) + +class test_automount(AutomountTest): + """ + Test the `automount` plugin. + """ + locname = u'testlocation' + mapname = u'testmap' + keyname = u'testkey' + keyname_rename = u'testkey_rename' + keyname2 = u'testkey2' + description = u'description of map' + info = u'ro' + newinfo = u'rw' + map_kw = {'automountmapname': mapname, 'description': description, 'raw': True} + key_kw = {'automountkey': keyname, 'automountinformation': info, 'raw': True} + key_kw2 = {'automountkey': keyname2, 'automountinformation': info, 'raw': True} + + tofiles_output = textwrap.dedent(u""" + /etc/auto.master: + /-\t/etc/auto.direct + --------------------------- + /etc/auto.direct: + + maps not connected to /etc/auto.master: + --------------------------- + /etc/testmap: + testkey2\tro + """).strip() + + def test_0_automountlocation_add(self): + """ + Test adding a location `xmlrpc.automountlocation_add` method. + """ + ret = self.failsafe_add( + api.Object.automountlocation, self.locname + ) + entry = ret['result'] + assert_attr_equal(entry, 'cn', self.locname) + + def test_1_automountmap_add(self): + """ + Test adding a map `xmlrpc.automountmap_add` method. + """ + res = api.Command['automountmap_add'](self.locname, **self.map_kw)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + def test_2_automountkey_add(self): + """ + Test adding a key using `xmlrpc.automountkey_add` method. + """ + res = api.Command['automountkey_add'](self.locname, self.mapname, **self.key_kw2)['result'] + assert res + assert_attr_equal(res, 'automountkey', self.keyname2) + + def test_3_automountkey_add(self): + """ + Test adding a key using `xmlrpc.automountkey_add` method. + """ + res = api.Command['automountkey_add'](self.locname, self.mapname, **self.key_kw)['result'] + assert res + assert_attr_equal(res, 'automountkey', self.keyname) + + @raises(errors.DuplicateEntry) + def test_4_automountkey_add(self): + """ + Test adding a duplicate key using `xmlrpc.automountkey_add` method. + """ + res = api.Command['automountkey_add'](self.locname, self.mapname, **self.key_kw) + + def test_5_automountmap_show(self): + """ + Test the `xmlrpc.automountmap_show` method. + """ + res = api.Command['automountmap_show'](self.locname, self.mapname, raw=True)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + def test_6_automountmap_find(self): + """ + Test the `xmlrpc.automountmap_find` method. + """ + res = api.Command['automountmap_find'](self.locname, self.mapname, raw=True)['result'] + assert_attr_equal(res[0], 'automountmapname', self.mapname) + + def test_7_automountkey_show(self): + """ + Test the `xmlrpc.automountkey_show` method. + """ + showkey_kw={'automountkey': self.keyname, 'automountinformation' : self.info, 'raw': True} + res = api.Command['automountkey_show'](self.locname, self.mapname, **showkey_kw)['result'] + assert res + assert_attr_equal(res, 'automountkey', self.keyname) + assert_attr_equal(res, 'automountinformation', self.info) + + def test_8_automountkey_find(self): + """ + Test the `xmlrpc.automountkey_find` method. + """ + res = api.Command['automountkey_find'](self.locname, self.mapname, raw=True)['result'] + assert res + assert len(res) == 2 + assert_attr_equal(res[0], 'automountkey', self.keyname) + assert_attr_equal(res[0], 'automountinformation', self.info) + + def test_9_automountkey_mod(self): + """ + Test the `xmlrpc.automountkey_mod` method. + """ + self.key_kw['newautomountinformation'] = self.newinfo + self.key_kw['rename'] = self.keyname_rename + res = api.Command['automountkey_mod'](self.locname, self.mapname, **self.key_kw)['result'] + assert res + assert_attr_equal(res, 'automountinformation', self.newinfo) + assert_attr_equal(res, 'automountkey', self.keyname_rename) + + def test_a_automountmap_mod(self): + """ + Test the `xmlrpc.automountmap_mod` method. + """ + mod_kw = {'description': u'new description'} + res = api.Command['automountmap_mod'](self.locname, self.mapname, **mod_kw)['result'] + assert res + assert_attr_equal(res, 'description', 'new description') + + def test_a2_automountmap_tofiles(self): + """ + Test the `automountlocation_tofiles` command. + """ + res = api.Command['automountlocation_tofiles'](self.locname) + assert_deepequal(dict( + result=dict( + keys={'auto.direct': ()}, + orphanmaps=(dict( + dn=DN(('automountmapname', self.mapname), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), + description=(u'description of map',), + automountmapname=(u'testmap',)),), + orphankeys=[( + dict( + dn=DN(('description', self.keyname2), + ('automountmapname', 'testmap'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), + automountkey=(self.keyname2,), + description=(self.keyname2,), + automountinformation=(u'ro',), + ), + dict( + dn=DN(('description', self.keyname_rename), + ('automountmapname', 'testmap'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), + automountkey=(self.keyname_rename,), + description=(self.keyname_rename,), + automountinformation=(u'rw',), + ))], + maps=( + dict( + dn=DN(('description', '/- auto.direct'), + ('automountmapname', 'auto.master'), + ('cn', self.locname), + ('cn', 'automount'), api.env.basedn), + automountkey=(u'/-',), + description=(u'/- auto.direct',), + automountinformation=(u'auto.direct',) + ), + ))), res) + + # Also check the CLI output + + self.check_tofiles() + + def test_b_automountkey_del(self): + """ + Test the `xmlrpc.automountkey_del` method. + """ + delkey_kw={'automountkey': self.keyname_rename, 'automountinformation' : self.newinfo} + res = api.Command['automountkey_del'](self.locname, self.mapname, **delkey_kw)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountkey_show'](self.locname, self.mapname, **delkey_kw) + + def test_c_automountlocation_del(self): + """ + Test the `xmlrpc.automountlocation_del` method. + """ + res = api.Command['automountlocation_del'](self.locname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountlocation_show'](self.locname) + + def test_d_automountmap_del(self): + """ + Test that the `xmlrpc.automountlocation_del` method removes all maps and keys + """ + # Verify that the second key we added is gone + key_kw = {'automountkey': self.keyname2, 'automountinformation': self.info, 'raw': True} + with assert_raises(errors.NotFound): + api.Command['automountkey_show'](self.locname, self.mapname, **key_kw) + + +class test_automount_direct(AutomountTest): + """ + Test the `automount` plugin indirect map functionality. + """ + locname = u'testlocation' + mapname = u'auto.direct2' + keyname = u'/-' + direct_kw = { 'key' : keyname } + + tofiles_output = textwrap.dedent(u""" + /etc/auto.master: + /-\t/etc/auto.direct + /-\t/etc/auto.direct2 + --------------------------- + /etc/auto.direct: + --------------------------- + /etc/auto.direct2: + + maps not connected to /etc/auto.master: + """).strip() + + def test_0_automountlocation_add(self): + """ + Test adding a location. + """ + res = api.Command['automountlocation_add'](self.locname, raw=True)['result'] + assert res + assert_attr_equal(res, 'cn', self.locname) + + def test_1_automountmap_add_direct(self): + """ + Test adding a second direct map with a different info + """ + res = api.Command['automountmap_add_indirect'](self.locname, self.mapname, **self.direct_kw)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + @raises(errors.DuplicateEntry) + def test_2_automountmap_add_duplicate(self): + """ + Test adding a duplicate direct map. + """ + res = api.Command['automountmap_add_indirect'](self.locname, self.mapname, **self.direct_kw)['result'] + + def test_2a_automountmap_tofiles(self): + """Test the `automountmap_tofiles` command""" + self.check_tofiles() + + def test_3_automountlocation_del(self): + """ + Remove the location. + """ + res = api.Command['automountlocation_del'](self.locname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verity that it is gone + with assert_raises(errors.NotFound): + api.Command['automountlocation_show'](self.locname) + + def test_z_import_roundtrip(self): + """Check automountlocation_tofiles/automountlocation_import roundtrip + """ + self.check_import_roundtrip() + + +class test_automount_indirect(AutomountTest): + """ + Test the `automount` plugin indirect map functionality. + """ + locname = u'testlocation' + mapname = u'auto.home' + keyname = u'/home' + parentmap = u'auto.master' + map_kw = {'key': keyname, 'parentmap': parentmap, 'raw': True} + key_kw = {'automountkey': keyname, 'automountinformation': mapname} + + tofiles_output = textwrap.dedent(u""" + /etc/auto.master: + /-\t/etc/auto.direct + /home\t/etc/auto.home + --------------------------- + /etc/auto.direct: + --------------------------- + /etc/auto.home: + + maps not connected to /etc/auto.master: + """).strip() + + def test_0_automountlocation_add(self): + """ + Test adding a location. + """ + res = api.Command['automountlocation_add'](self.locname, raw=True)['result'] + assert res + assert_attr_equal(res, 'cn', self.locname) + + def test_1_automountmap_add_indirect(self): + """ + Test adding an indirect map. + """ + res = api.Command['automountmap_add_indirect'](self.locname, self.mapname, **self.map_kw)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + @raises(errors.DuplicateEntry) + def test_1a_automountmap_add_indirect(self): + """ + Test adding a duplicate indirect map. + """ + api.Command['automountmap_add_indirect'](self.locname, self.mapname, **self.map_kw)['result'] + + def test_2_automountmap_show(self): + """ + Test the `xmlrpc.automountmap_show` method. + """ + res = api.Command['automountmap_show'](self.locname, self.mapname, raw=True)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + def test_2a_automountmap_tofiles(self): + """Test the `automountmap_tofiles` command""" + self.check_tofiles() + + def test_3_automountkey_del(self): + """ + Remove the indirect key /home. + """ + res = api.Command['automountkey_del'](self.locname, self.parentmap, **self.key_kw)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountkey_show'](self.locname, self.parentmap, **self.key_kw) + + def test_4_automountmap_del(self): + """ + Remove the indirect map for auto.home. + """ + res = api.Command['automountmap_del'](self.locname, self.mapname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountmap_show'](self.locname, self.mapname) + + def test_5_automountlocation_del(self): + """ + Remove the location. + """ + res = api.Command['automountlocation_del'](self.locname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verity that it is gone + with assert_raises(errors.NotFound): + api.Command['automountlocation_show'](self.locname) + + def test_z_import_roundtrip(self): + """Check automountlocation_tofiles/automountlocation_import roundtrip + """ + self.check_import_roundtrip() + +class test_automount_indirect_no_parent(AutomountTest): + """ + Test the `automount` plugin Indirect map function. + """ + locname = u'testlocation' + mapname = u'auto.home' + keyname = u'/home' + mapname2 = u'auto.direct2' + keyname2 = u'direct2' + parentmap = u'auto.master' + map_kw = {'key': keyname, 'raw': True} + map_kw2 = {'key': keyname2, 'raw': True} + + tofiles_output = textwrap.dedent(u""" + /etc/auto.master: + /-\t/etc/auto.direct + /home\t/etc/auto.home + --------------------------- + /etc/auto.direct: + --------------------------- + /etc/auto.home: + direct2\t-fstype=autofs ldap:auto.direct2 + + maps not connected to /etc/auto.master: + --------------------------- + /etc/auto.direct2: + """).strip() + + def test_0_automountlocation_add(self): + """ + Test adding a location. + """ + res = api.Command['automountlocation_add'](self.locname, raw=True)['result'] + assert res + assert_attr_equal(res, 'cn', self.locname) + + def test_1_automountmap_add_indirect(self): + """ + Test adding an indirect map with default parent. + """ + res = api.Command['automountmap_add_indirect'](self.locname, self.mapname, **self.map_kw)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname) + + def test_2_automountkey_show(self): + """ + Test the `xmlrpc.automountkey_show` method with default parent. + """ + showkey_kw = {'automountkey': self.keyname, 'automountinformation': self.mapname, 'raw': True} + res = api.Command['automountkey_show'](self.locname, self.parentmap, **showkey_kw)['result'] + assert res + assert_attr_equal(res, 'automountkey', self.keyname) + + def test_2a_automountmap_add_indirect(self): + """ + Test adding an indirect map with default parent. + """ + res = api.Command['automountmap_add_indirect'](self.locname, + u'auto.direct2', parentmap=self.mapname, **self.map_kw2)['result'] + assert res + assert_attr_equal(res, 'automountmapname', self.mapname2) + + def test_2b_automountmap_tofiles(self): + """Test the `automountmap_tofiles` command""" + self.check_tofiles() + + def test_3_automountkey_del(self): + """ + Remove the indirect key /home. + """ + delkey_kw={'automountkey': self.keyname, 'automountinformation': self.mapname} + res = api.Command['automountkey_del'](self.locname, self.parentmap, **delkey_kw)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountkey_show'](self.locname, self.parentmap, **delkey_kw) + + def test_4_automountmap_del(self): + """ + Remove the indirect map for auto.home. + """ + res = api.Command['automountmap_del'](self.locname, self.mapname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['automountmap_show'](self.locname, self.mapname) + + def test_5_automountlocation_del(self): + """ + Remove the location. + """ + res = api.Command['automountlocation_del'](self.locname)['result'] + assert res + assert_attr_equal(res, 'failed', '') + + # Verity that it is gone + with assert_raises(errors.NotFound): + api.Command['automountlocation_show'](self.locname) diff --git a/ipatests/test_xmlrpc/test_baseldap_plugin.py b/ipatests/test_xmlrpc/test_baseldap_plugin.py new file mode 100644 index 000000000..6a8501f76 --- /dev/null +++ b/ipatests/test_xmlrpc/test_baseldap_plugin.py @@ -0,0 +1,159 @@ +# Authors: +# Petr Viktorin <pviktori@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib.plugins.baseldap` module. +""" + +from ipalib import errors +from ipalib.plugins import baseldap + + +def test_exc_wrapper(): + """Test the CallbackInterface._exc_wrapper helper method""" + handled_exceptions = [] + + class test_callback(baseldap.BaseLDAPCommand): + """Fake IPA method""" + def test_fail(self): + self._exc_wrapper([], {}, self.fail)(1, 2, a=1, b=2) + + def fail(self, *args, **kwargs): + assert args == (1, 2) + assert kwargs == dict(a=1, b=2) + raise errors.ExecutionError('failure') + + instance = test_callback() + + # Test with one callback first + + @test_callback.register_exc_callback + def handle_exception(self, keys, options, e, call_func, *args, **kwargs): + assert args == (1, 2) + assert kwargs == dict(a=1, b=2) + handled_exceptions.append(type(e)) + + instance.test_fail() + assert handled_exceptions == [errors.ExecutionError] + + # Test with another callback added + + handled_exceptions = [] + + def dont_handle(self, keys, options, e, call_func, *args, **kwargs): + assert args == (1, 2) + assert kwargs == dict(a=1, b=2) + handled_exceptions.append(None) + raise e + test_callback.register_exc_callback(dont_handle, first=True) + + instance.test_fail() + assert handled_exceptions == [None, errors.ExecutionError] + + +def test_callback_registration(): + class callbacktest_base(baseldap.CallbackInterface): + _callback_registry = dict(test={}) + + def test_callback(self, param): + messages.append(('Base test_callback', param)) + + def registered_callback(self, param): + messages.append(('Base registered callback', param)) + callbacktest_base.register_callback('test', registered_callback) + + class SomeClass(object): + def registered_callback(self, command, param): + messages.append(('Registered callback from another class', param)) + callbacktest_base.register_callback('test', SomeClass().registered_callback) + + class callbacktest_subclass(callbacktest_base): + pass + + def subclass_callback(self, param): + messages.append(('Subclass registered callback', param)) + callbacktest_subclass.register_callback('test', subclass_callback) + + + messages = [] + instance = callbacktest_base() + for callback in instance.get_callbacks('test'): + callback(instance, 42) + assert messages == [ + ('Base test_callback', 42), + ('Base registered callback', 42), + ('Registered callback from another class', 42)] + + messages = [] + instance = callbacktest_subclass() + for callback in instance.get_callbacks('test'): + callback(instance, 42) + assert messages == [ + ('Base test_callback', 42), + ('Subclass registered callback', 42)] + + +def test_exc_callback_registration(): + messages = [] + class callbacktest_base(baseldap.BaseLDAPCommand): + """A method superclass with an exception callback""" + def exc_callback(self, keys, options, exc, call_func, *args, **kwargs): + """Let the world know we saw the error, but don't handle it""" + messages.append('Base exc_callback') + raise exc + + def test_fail(self): + """Raise a handled exception""" + try: + self._exc_wrapper([], {}, self.fail)(1, 2, a=1, b=2) + except Exception: + pass + + def fail(self, *args, **kwargs): + """Raise an error""" + raise errors.ExecutionError('failure') + + base_instance = callbacktest_base() + + class callbacktest_subclass(callbacktest_base): + pass + + @callbacktest_subclass.register_exc_callback + def exc_callback(self, keys, options, exc, call_func, *args, **kwargs): + """Subclass's private exception callback""" + messages.append('Subclass registered callback') + raise exc + + subclass_instance = callbacktest_subclass() + + # Make sure exception in base class is only handled by the base class + base_instance.test_fail() + assert messages == ['Base exc_callback'] + + + @callbacktest_base.register_exc_callback + def exc_callback(self, keys, options, exc, call_func, *args, **kwargs): + """Callback on super class; doesn't affect the subclass""" + messages.append('Superclass registered callback') + raise exc + + # Make sure exception in subclass is only handled by both + messages = [] + subclass_instance.test_fail() + assert messages == ['Base exc_callback', 'Subclass registered callback'] diff --git a/ipatests/test_xmlrpc/test_batch_plugin.py b/ipatests/test_xmlrpc/test_batch_plugin.py new file mode 100644 index 000000000..2b056c93f --- /dev/null +++ b/ipatests/test_xmlrpc/test_batch_plugin.py @@ -0,0 +1,232 @@ +# Authors: +# Petr Viktorin <pviktori@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/batch.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from ipatests.util import assert_equal, Fuzzy, assert_deepequal +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +group1 = u'testgroup1' + + +def deepequal_list(*expected): + """Factory for a function that checks a list + + The created function asserts items of a list are "deepequal" to the given + argument. Unlike using assert_deepequal directly, the order matters. + """ + def checker(got): + if len(expected) != len(got): + raise AssertionError('Expected %s entries, got %s\n\n%s\n%s' % + (len(expected), len(got), expected, got)) + for e, g in zip(expected, got): + assert_deepequal(e, g) + return True + return checker + + +class test_batch(Declarative): + + cleanup_commands = [ + ('group_del', [group1], {}), + ] + + tests = [ + + dict( + desc='Batch ping', + command=('batch', [dict(method='ping', params=([], {}))], {}), + expected=dict( + count=1, + results=[ + dict(summary=Fuzzy('IPA server version .*'), error=None), + ] + ), + ), + + dict( + desc='Batch two pings', + command=('batch', [dict(method='ping', params=([], {}))] * 2, {}), + expected=dict( + count=2, + results=[ + dict(summary=Fuzzy('IPA server version .*'), error=None), + dict(summary=Fuzzy('IPA server version .*'), error=None), + ] + ), + ), + + dict( + desc='Create and deleting a group', + command=('batch', [ + dict(method='group_add', + params=([group1], dict(description=u'Test desc 1'))), + dict(method='group_del', params=([group1], dict())), + ], {}), + expected=dict( + count=2, + results=deepequal_list( + dict( + value=group1, + summary=u'Added group "testgroup1"', + result=dict( + cn=[group1], + description=[u'Test desc 1'], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + gidnumber=[fuzzy_digits], + dn=DN(('cn', 'testgroup1'), + ('cn', 'groups'), + ('cn', 'accounts'), + api.env.basedn), + ), + error=None), + dict( + summary=u'Deleted group "%s"' % group1, + result=dict(failed=u''), + value=group1, + error=None), + ), + ), + ), + + dict( + desc='Try to delete nonexistent group twice', + command=('batch', [ + dict(method='group_del', params=([group1], dict())), + dict(method='group_del', params=([group1], dict())), + ], {}), + expected=dict( + count=2, + results=[ + dict( + error=u'%s: group not found' % group1, + error_name=u'NotFound', + error_code=4001, + ), + dict( + error=u'%s: group not found' % group1, + error_name=u'NotFound', + error_code=4001, + ), + ], + ), + ), + + dict( + desc='Try to delete non-existent group first, then create it', + command=('batch', [ + dict(method='group_del', params=([group1], dict())), + dict(method='group_add', + params=([group1], dict(description=u'Test desc 1'))), + ], {}), + expected=dict( + count=2, + results=deepequal_list( + dict( + error=u'%s: group not found' % group1, + error_name=u'NotFound', + error_code=4001, + ), + dict( + value=group1, + summary=u'Added group "testgroup1"', + result=dict( + cn=[group1], + description=[u'Test desc 1'], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + gidnumber=[fuzzy_digits], + dn=DN(('cn', 'testgroup1'), + ('cn', 'groups'), + ('cn', 'accounts'), + api.env.basedn), + ), + error=None), + ), + ), + ), + + dict( + desc='Try bad command invocations', + command=('batch', [ + # bad command name + dict(method='nonexistent_ipa_command', params=([], dict())), + # dash, not underscore, in command name + dict(method='user-del', params=([], dict())), + # missing command name + dict(params=([group1], dict())), + # missing params + dict(method='user_del'), + # missing required argument + dict(method='user_add', params=([], dict())), + # missing required option + dict(method='group_add', params=([group1], dict())), + # bad type + dict(method='group_add', params=([group1], dict( + description=u't', gidnumber=u'bad'))), + ], {}), + expected=dict( + count=7, + results=deepequal_list( + dict( + error=u"unknown command 'nonexistent_ipa_command'", + error_name=u'CommandError', + error_code=905, + ), + dict( + error=u"unknown command 'user-del'", + error_name=u'CommandError', + error_code=905, + ), + dict( + error=u"'method' is required", + error_name=u'RequirementError', + error_code=3007, + ), + dict( + error=u"'params' is required", + error_name=u'RequirementError', + error_code=3007, + ), + dict( + error=u"'givenname' is required", + error_name=u'RequirementError', + error_code=3007, + ), + dict( + error=u"'description' is required", + error_name=u'RequirementError', + error_code=3007, + ), + dict( + error=Fuzzy(u"invalid 'gid'.*"), + error_name=u'ConversionError', + error_code=3008, + ), + ), + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_cert_plugin.py b/ipatests/test_xmlrpc/test_cert_plugin.py new file mode 100644 index 000000000..508e9141a --- /dev/null +++ b/ipatests/test_xmlrpc/test_cert_plugin.py @@ -0,0 +1,454 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2009,2013 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/cert.py` module against a RA. +""" + +import sys +import os +import shutil +from nose.tools import raises, assert_raises # pylint: disable=E0611 + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors +from ipalib import x509 +import tempfile +from ipapython import ipautil +import nose +import base64 +from ipapython.dn import DN + +# So we can save the cert from issuance and compare it later +cert = None +newcert = None + +def is_db_configured(): + """ + Raise an exception if we are testing against lite-server and the + developer cert database is configured. + """ + aliasdir = api.env.dot_ipa + os.sep + 'alias' + os.sep + '.pwd' + + if (api.env.xmlrpc_uri == u'http://localhost:8888/ipa/xml' and + not ipautil.file_exists(aliasdir)): + raise nose.SkipTest('developer CA not configured in %s' % aliasdir) + +# Test setup +# +# This test needs a configured CA behind it in order to work properly +# +# To test against Apache directly then no changes are required. Just be +# sure the xmlrpc_uri in ~/.ipa/default.conf points to Apache. +# +# To test against Dogtag CA in the lite-server: +# +# - Copy the 3 NSS db files from /etc/httpd/alias to ~/.ipa/alias +# - Copy /etc/httpd/alias/pwdfile.txt to ~/.ipa/alias/.pwd. +# - Change ownership of these files to be readable by you. +# +# The API tested depends on the value of ~/.ipa/default/ra_plugin when +# running as the lite-server. + +class test_cert(XMLRPC_test): + + @classmethod + def setUpClass(cls): + super(test_cert, cls).setUpClass() + + if 'cert_request' not in api.Command: + raise nose.SkipTest('cert_request not registered') + + is_db_configured() + + def run_certutil(self, args, stdin=None): + new_args = ["/usr/bin/certutil", "-d", self.reqdir] + new_args = new_args + args + return ipautil.run(new_args, stdin) + + def setUp(self): + super(test_cert, self).setUp() + self.reqdir = tempfile.mkdtemp(prefix = "tmp-") + self.reqfile = self.reqdir + "/test.csr" + self.pwname = self.reqdir + "/pwd" + + # Create an empty password file + fp = open(self.pwname, "w") + fp.write("\n") + fp.close() + + # Create our temporary NSS database + self.run_certutil(["-N", "-f", self.pwname]) + + self.subject = DN(('CN', self.host_fqdn), x509.subject_base()) + + def tearDown(self): + super(test_cert, self).tearDown() + shutil.rmtree(self.reqdir, ignore_errors=True) + + def generateCSR(self, subject): + self.run_certutil(["-R", "-s", subject, + "-o", self.reqfile, + "-z", "/etc/group", + "-f", self.pwname, + "-a", + ]) + fp = open(self.reqfile, "r") + data = fp.read() + fp.close() + return data + + """ + Test the `cert` plugin. + """ + host_fqdn = u'ipatestcert.%s' % api.env.domain + service_princ = u'test/%s@%s' % (host_fqdn, api.env.realm) + + def test_0001_cert_add(self): + """ + Test the `xmlrpc.cert_request` method without --add. + + This should fail because the service principal doesn't exist + """ + # First create the host that will use this policy + res = api.Command['host_add'](self.host_fqdn, force= True)['result'] + + csr = unicode(self.generateCSR(str(self.subject))) + with assert_raises(errors.NotFound): + res = api.Command['cert_request'](csr, principal=self.service_princ) + + def test_0002_cert_add(self): + """ + Test the `xmlrpc.cert_request` method with --add. + """ + # Our host should exist from previous test + global cert + + csr = unicode(self.generateCSR(str(self.subject))) + res = api.Command['cert_request'](csr, principal=self.service_princ, add=True)['result'] + assert DN(res['subject']) == self.subject + # save the cert for the service_show/find tests + cert = res['certificate'] + + def test_0003_service_show(self): + """ + Verify that service-show has the right certificate using service-show. + """ + global cert + + res = api.Command['service_show'](self.service_princ)['result'] + assert base64.b64encode(res['usercertificate'][0]) == cert + + def test_0004_service_find(self): + """ + Verify that service-find has the right certificate using service-find. + """ + global cert + + # Assume there is only one service + res = api.Command['service_find'](self.service_princ)['result'] + assert base64.b64encode(res[0]['usercertificate'][0]) == cert + + def test_0005_cert_renew(self): + """ + Issue a new certificate for a service + """ + global newcert + + csr = unicode(self.generateCSR(str(self.subject))) + res = api.Command['cert_request'](csr, principal=self.service_princ)['result'] + assert DN(res['subject']) == self.subject + # save the cert for the service_show/find tests + newcert = res['certificate'] + + def test_0006_service_show(self): + """ + Verify the new certificate with service-show. + """ + global cert, newcert + + res = api.Command['service_show'](self.service_princ)['result'] + # It should no longer match our old cert + assert base64.b64encode(res['usercertificate'][0]) != cert + # And it should match the new one + assert base64.b64encode(res['usercertificate'][0]) == newcert + + def test_0007_cleanup(self): + """ + Clean up cert test data + """ + # Now clean things up + api.Command['host_del'](self.host_fqdn) + + # Verify that the service is gone + res = api.Command['service_find'](self.service_princ) + assert res['count'] == 0 + +class test_cert_find(XMLRPC_test): + + @classmethod + def setUpClass(cls): + super(test_cert_find, cls).setUpClass() + + if 'cert_find' not in api.Command: + raise nose.SkipTest('cert_find not registered') + + if api.env.ra_plugin != 'dogtag': + raise nose.SkipTest('cert_find for dogtag CA only') + + is_db_configured() + + """ + Test the `cert-find` command. + """ + short = api.env.host.replace('.' + api.env.domain, '') + + def test_0001_find_all(self): + """ + Search for all certificates. + + We don't know how many we'll get but there should be at least 10 + by default. + """ + res = api.Command['cert_find']() + assert 'count' in res and res['count'] >= 10 + + def test_0002_find_CA(self): + """ + Search for the CA certificate. + """ + res = api.Command['cert_find'](subject=u'Certificate Authority') + assert 'count' in res and res['count'] == 1 + + def test_0003_find_OCSP(self): + """ + Search for the OCSP certificate. + """ + res = api.Command['cert_find'](subject=u'OCSP Subsystem') + + def test_0004_find_this_host(self): + """ + Find all certificates for this IPA server + """ + res = api.Command['cert_find'](subject=api.env.host) + assert 'count' in res and res['count'] > 1 + + def test_0005_find_this_host_exact(self): + """ + Find all certificates for this IPA server (exact) + """ + res = api.Command['cert_find'](subject=api.env.host, exactly=True) + assert 'count' in res and res['count'] > 1 + + def test_0006_find_this_short_host_exact(self): + """ + Find all certificates for this IPA server short name (exact) + """ + res = api.Command['cert_find'](subject=self.short, exactly=True) + assert 'count' in res and res['count'] == 0 + + def test_0007_find_revocation_reason_0(self): + """ + Find all certificates with revocation reason 0 + """ + res = api.Command['cert_find'](revocation_reason=0) + assert 'count' in res and res['count'] == 0 + + def test_0008_find_revocation_reason_1(self): + """ + Find all certificates with revocation reason 1 + """ + res = api.Command['cert_find'](revocation_reason=1) + assert 'count' in res and res['count'] == 0 + + def test_0009_find_revocation_reason_2(self): + """ + Find all certificates with revocation reason 2 + """ + res = api.Command['cert_find'](revocation_reason=2) + assert 'count' in res and res['count'] == 0 + + def test_0010_find_revocation_reason_3(self): + """ + Find all certificates with revocation reason 3 + """ + res = api.Command['cert_find'](revocation_reason=3) + assert 'count' in res and res['count'] == 0 + + def test_0011_find_revocation_reason_4(self): + """ + Find all certificates with revocation reason 4 + + There is no way to know in advance how many revoked certificates + we'll have but in the context of make-test we'll have at least one. + """ + res = api.Command['cert_find'](revocation_reason=4) + assert 'count' in res and res['count'] >= 1 + + def test_0012_find_revocation_reason_5(self): + """ + Find all certificates with revocation reason 5 + """ + res = api.Command['cert_find'](revocation_reason=5) + assert 'count' in res and res['count'] == 0 + + def test_0013_find_revocation_reason_6(self): + """ + Find all certificates with revocation reason 6 + """ + res = api.Command['cert_find'](revocation_reason=6) + assert 'count' in res and res['count'] == 0 + + # There is no revocation reason #7 + + def test_0014_find_revocation_reason_8(self): + """ + Find all certificates with revocation reason 8 + """ + res = api.Command['cert_find'](revocation_reason=8) + assert 'count' in res and res['count'] == 0 + + def test_0015_find_revocation_reason_9(self): + """ + Find all certificates with revocation reason 9 + """ + res = api.Command['cert_find'](revocation_reason=9) + assert 'count' in res and res['count'] == 0 + + def test_0016_find_revocation_reason_10(self): + """ + Find all certificates with revocation reason 10 + """ + res = api.Command['cert_find'](revocation_reason=10) + assert 'count' in res and res['count'] == 0 + + def test_0017_find_by_issuedon(self): + """ + Find all certificates issued since 2008 + """ + res = api.Command['cert_find'](issuedon_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0018_find_through_issuedon(self): + """ + Find all certificates issued through 2008 + """ + res = api.Command['cert_find'](issuedon_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0019_find_notvalid_before(self): + """ + Find all certificates valid not before 2008 + """ + res = api.Command['cert_find'](validnotbefore_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0020_find_notvalid_before(self): + """ + Find all certificates valid not before to 2100 + """ + res = api.Command['cert_find'](validnotbefore_to=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0021_find_notvalid_before(self): + """ + Find all certificates valid not before 2100 + """ + res = api.Command['cert_find'](validnotbefore_from=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0022_find_notvalid_before(self): + """ + Find all certificates valid not before to 2008 + """ + res = api.Command['cert_find'](validnotbefore_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0023_find_notvalid_after(self): + """ + Find all certificates valid not after 2008 + """ + res = api.Command['cert_find'](validnotafter_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0024_find_notvalid_after(self): + """ + Find all certificates valid not after to 2100 + """ + res = api.Command['cert_find'](validnotafter_to=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0025_find_notvalid_after(self): + """ + Find all certificates valid not after 2100 + """ + res = api.Command['cert_find'](validnotafter_from=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0026_find_notvalid_after(self): + """ + Find all certificates valid not after to 2008 + """ + res = api.Command['cert_find'](validnotafter_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0027_sizelimit_zero(self): + """ + Search with a sizelimit of 0 + """ + res = api.Command['cert_find'](sizelimit=0) + assert 'count' in res and res['count'] == 0 + + @raises(errors.ValidationError) + def test_0028_find_negative_size(self): + """ + Search with a negative sizelimit + """ + res = api.Command['cert_find'](sizelimit=-100) + + def test_0029_search_for_notfound(self): + """ + Search for a host that isn't there. + """ + res = api.Command['cert_find'](subject=u'notfound') + assert 'count' in res and res['count'] == 0 + + def test_0030_search_for_testcerts(self): + """ + Search for certs created in other tests + """ + res = api.Command['cert_find'](subject=u'ipatestcert.%s' % api.env.domain) + assert 'count' in res and res['count'] >= 1 + + @raises(errors.ValidationError) + def test_0031_search_on_invalid_date(self): + """ + Search using invalid date format + """ + res = api.Command['cert_find'](issuedon_from=u'xyz') diff --git a/ipatests/test_xmlrpc/test_config_plugin.py b/ipatests/test_xmlrpc/test_config_plugin.py new file mode 100644 index 000000000..3d9a31daf --- /dev/null +++ b/ipatests/test_xmlrpc/test_config_plugin.py @@ -0,0 +1,121 @@ +# Authors: +# Petr Viktorin <pviktori@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/config.py` module. +""" + +from ipalib import errors +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid + +class test_config(Declarative): + + cleanup_commands = [ + ] + + tests = [ + + dict( + desc='Try to add an unrelated objectclass to ipauserobjectclasses', + command=('config_mod', [], + dict(addattr=u'ipauserobjectclasses=ipahost')), + expected=dict( + result=lambda d: 'ipahost' in d['ipauserobjectclasses'], + value=u'', + summary=None, + ), + ), + + dict( + desc='Remove the unrelated objectclass from ipauserobjectclasses', + command=('config_mod', [], + dict(delattr=u'ipauserobjectclasses=ipahost')), + expected=dict( + result=lambda d: 'ipahost' not in d['ipauserobjectclasses'], + value=u'', + summary=None, + ), + ), + + dict( + desc='Try to remove ipausersearchfields', + command=('config_mod', [], + dict(delattr=u'ipausersearchfields=uid,givenname,sn,telephonenumber,ou,title')), + expected=errors.RequirementError(name='ipausersearchfields'), + ), + + dict( + desc='Try to set ipaselinuxusermapdefault not in selinux order list', + command=('config_mod', [], + dict(ipaselinuxusermapdefault=u'unknown_u:s0')), + expected=errors.ValidationError(name='ipaselinuxusermapdefault', + error='SELinux user map default user not in order list'), + ), + + dict( + desc='Try to set invalid ipaselinuxusermapdefault', + command=('config_mod', [], + dict(ipaselinuxusermapdefault=u'foo')), + expected=errors.ValidationError(name='ipaselinuxusermapdefault', + error='Invalid MLS value, must match s[0-15](-s[0-15])'), + ), + + dict( + desc='Try to set invalid ipaselinuxusermapdefault with setattr', + command=('config_mod', [], + dict(setattr=u'ipaselinuxusermapdefault=unknown_u:s0')), + expected=errors.ValidationError(name='ipaselinuxusermapdefault', + error='SELinux user map default user not in order list'), + ), + + dict( + desc='Try to set ipaselinuxusermaporder without ipaselinuxusermapdefault out of it', + command=('config_mod', [], + dict(ipaselinuxusermaporder=u'notfound_u:s0')), + expected=errors.ValidationError(name='ipaselinuxusermaporder', + error='SELinux user map default user not in order list'), + ), + + dict( + desc='Try to set invalid ipaselinuxusermaporder', + command=('config_mod', [], + dict(ipaselinuxusermaporder=u'$')), + expected=errors.ValidationError(name='ipaselinuxusermaporder', + error='A list of SELinux users delimited by $ expected'), + ), + + dict( + desc='Try to set invalid selinux user in ipaselinuxusermaporder', + command=('config_mod', [], + dict(ipaselinuxusermaporder=u'unconfined_u:s0-s0:c0.c1023$baduser$guest_u:s0')), + expected=errors.ValidationError(name='ipaselinuxusermaporder', + error='SELinux user \'baduser\' is not valid: Invalid MLS ' + 'value, must match s[0-15](-s[0-15])'), + ), + + dict( + desc='Try to set new selinux order and invalid default user', + command=('config_mod', [], + dict(ipaselinuxusermaporder=u'xguest_u:s0$guest_u:s0$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023', + ipaselinuxusermapdefault=u'unknown_u:s0')), + expected=errors.ValidationError(name='ipaselinuxusermapdefault', + error='SELinux user map default user not in order list'), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_delegation_plugin.py b/ipatests/test_xmlrpc/test_delegation_plugin.py new file mode 100644 index 000000000..f2cfc8302 --- /dev/null +++ b/ipatests/test_xmlrpc/test_delegation_plugin.py @@ -0,0 +1,300 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/delegation.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +delegation1 = u'testdelegation' +member1 = u'admins' + +class test_delegation(Declarative): + + cleanup_commands = [ + ('delegation_del', [delegation1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % delegation1), + ), + + + dict( + desc='Try to update non-existent %r' % delegation1, + command=('delegation_mod', [delegation1], dict(group=u'admins')), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % delegation1), + ), + + + dict( + desc='Try to delete non-existent %r' % delegation1, + command=('delegation_del', [delegation1], {}), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % delegation1), + ), + + + dict( + desc='Search for non-existent %r' % delegation1, + command=('delegation_find', [delegation1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 delegations matched', + result=[], + ), + ), + + dict( + desc='Try to create %r for non-existing member group' % delegation1, + command=( + 'delegation_add', [delegation1], dict( + attrs=u'street,c,l,st,postalCode', + permissions=u'write', + group=u'editors', + memberof=u'nonexisting', + ), + ), + expected=errors.NotFound(reason=u'nonexisting: group not found'), + ), + + # Note that we add postalCode but expect postalcode. This tests + # the attrs normalizer. + dict( + desc='Create %r' % delegation1, + command=( + 'delegation_add', [delegation1], dict( + attrs=[u'street', u'c', u'l', u'st', u'postalCode'], + permissions=u'write', + group=u'editors', + memberof=u'admins', + ) + ), + expected=dict( + value=delegation1, + summary=u'Added delegation "%s"' % delegation1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=[u'write'], + aciname=delegation1, + group=u'editors', + memberof=member1, + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % delegation1, + command=( + 'delegation_add', [delegation1], dict( + attrs=[u'street', u'c', u'l', u'st', u'postalCode'], + permissions=u'write', + group=u'editors', + memberof=u'admins', + ), + ), + expected=errors.DuplicateEntry(), + ), + + + dict( + desc='Retrieve %r' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=dict( + value=delegation1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'memberof': member1, + }, + ), + ), + + + dict( + desc='Retrieve %r with --raw' % delegation1, + command=('delegation_show', [delegation1], {'raw' : True}), + expected=dict( + value=delegation1, + summary=None, + result={ + 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('cn', 'admins'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'editors'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)) + }, + ), + ), + + + dict( + desc='Search for %r' % delegation1, + command=('delegation_find', [delegation1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'memberof': member1, + }, + ], + ), + ), + + + dict( + desc='Search for %r using --group filter' % delegation1, + command=('delegation_find', [delegation1], {'group': u'editors'}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'memberof': member1, + }, + ], + ), + ), + + + dict( + desc='Search for %r using --membergroup filter' % delegation1, + command=('delegation_find', [delegation1], {'memberof': member1}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'memberof': member1, + }, + ], + ), + ), + + + dict( + desc='Search for %r with --pkey-only' % delegation1, + command=('delegation_find', [delegation1], {'pkey_only' : True}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'aciname': delegation1, + }, + ], + ), + ), + + + dict( + desc='Search for %r with --raw' % delegation1, + command=('delegation_find', [delegation1], {'raw' : True}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'aci': u'(targetattr = "street || c || l || st || postalcode")(targetfilter = "(memberOf=%s)")(version 3.0;acl "delegation:testdelegation";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('cn', 'admins'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'editors'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)), + }, + ], + ), + ), + + + dict( + desc='Update %r' % delegation1, + command=( + 'delegation_mod', [delegation1], dict(permissions=u'read') + ), + expected=dict( + value=delegation1, + summary=u'Modified delegation "%s"' % delegation1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=[u'read'], + aciname=delegation1, + group=u'editors', + memberof=member1, + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=dict( + value=delegation1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'read'], + 'aciname': delegation1, + 'group': u'editors', + 'memberof': member1, + }, + ), + ), + + + dict( + desc='Delete %r' % delegation1, + command=('delegation_del', [delegation1], {}), + expected=dict( + result=True, + value=delegation1, + summary=u'Deleted delegation "%s"' % delegation1, + ) + ), + + ] diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py new file mode 100644 index 000000000..ea9b70e36 --- /dev/null +++ b/ipatests/test_xmlrpc/test_dns_plugin.py @@ -0,0 +1,1511 @@ +# Authors: +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/dns.py` module. +""" + +import nose +from ipalib import api, errors +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid + +dnszone1 = u'dnszone.test' +dnszone1_dn = DN(('idnsname',dnszone1), api.env.container_dns, api.env.basedn) +dnszone1_mname = u'ns1.%s.' % dnszone1 +dnszone1_mname_dn = DN(('idnsname','ns1'), dnszone1_dn) +dnszone1_rname = u'root.%s.' % dnszone1 +dnszone1_permission = u'Manage DNS zone %s' % dnszone1 +dnszone1_permission_dn = DN(('cn',dnszone1_permission), + api.env.container_permission,api.env.basedn) +dnszone1_txtrec_dn = DN(('idnsname', '_kerberos'), dnszone1_dn) +dnszone2 = u'dnszone2.test' +dnszone2_dn = DN(('idnsname', dnszone2), api.env.container_dns, api.env.basedn) +dnszone2_mname = u'ns1.%s.' % dnszone2 +dnszone2_rname = u'root.%s.' % dnszone2 +revdnszone1 = u'15.142.80.in-addr.arpa.' +revdnszone1_ip = u'80.142.15.0/24' +revdnszone1_dn = DN(('idnsname', revdnszone1), api.env.container_dns, api.env.basedn) +revdnszone2 = u'16.142.80.in-addr.arpa.' +revdnszone2_ip = u'80.142.16.0' +revdnszone2_dn = DN(('idnsname',revdnszone2), api.env.container_dns, api.env.basedn) +dnsres1 = u'testdnsres' +dnsres1_dn = DN(('idnsname',dnsres1), dnszone1_dn) +dnsres1_renamed = u'testdnsres-renamed' +dnsrev1 = u'80' +dnsrev1_dn = DN(('idnsname',dnsrev1), revdnszone1_dn) +dnsrev2 = u'81' +dnsrev2_dn = DN(('idnsname',dnsrev2), revdnszone1_dn) +dnsrescname = u'testcnamerec' +dnsrescname_dn = DN(('idnsname',dnsrescname), dnszone1_dn) +dnsresdname = u'testdns-dname' +dnsresdname_dn = DN(('idnsname',dnsresdname), dnszone1_dn) + +class test_dns(Declarative): + + @classmethod + def setUpClass(cls): + super(test_dns, cls).setUpClass() + + if not api.Backend.xmlclient.isconnected(): + api.Backend.xmlclient.connect(fallback=False) + try: + api.Command['dnszone_add'](dnszone1, + idnssoamname = dnszone1_mname, + idnssoarname = dnszone1_rname, + force = True, + ) + api.Command['dnszone_del'](dnszone1) + except errors.NotFound: + raise nose.SkipTest('DNS is not configured') + except errors.DuplicateEntry: + pass + + cleanup_commands = [ + ('dnszone_del', [dnszone1, dnszone2, revdnszone1, revdnszone2], + {'continue': True}), + ('dnsconfig_mod', [], {'idnsforwarders' : None, + 'idnsforwardpolicy' : None, + 'idnsallowsyncptr' : None, + 'idnszonerefresh' : None, + }), + ('permission_del', [dnszone1_permission], {'force': True}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent zone %r' % dnszone1, + command=('dnszone_show', [dnszone1], {}), + expected=errors.NotFound( + reason=u'%s: DNS zone not found' % dnszone1), + ), + + + dict( + desc='Try to update non-existent zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnssoaminimum': 3500}), + expected=errors.NotFound( + reason=u'%s: DNS zone not found' % dnszone1), + ), + + + dict( + desc='Try to delete non-existent zone %r' % dnszone1, + command=('dnszone_del', [dnszone1], {}), + expected=errors.NotFound( + reason=u'%s: DNS zone not found' % dnszone1), + ), + + + dict( + desc='Try to create zone with invalid name', + command=( + 'dnszone_add', [u'invalid zone'], { + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected=errors.ValidationError(name='name', + error=u'only letters, numbers, and - are allowed. ' + + u'DNS label may not start or end with -'), + ), + + + dict( + desc='Create zone %r' % dnszone1, + command=( + 'dnszone_add', [dnszone1], { + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone1_mname], + 'nsrecord': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Try to create duplicate zone %r' % dnszone1, + command=( + 'dnszone_add', [dnszone1], { + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected=errors.DuplicateEntry( + message=u'DNS zone with name "%s" already exists' % dnszone1), + ), + + dict( + desc='Try to create a zone with nonexistent NS entry', + command=( + 'dnszone_add', [dnszone2], { + 'idnssoamname': dnszone2_mname, + 'idnssoarname': dnszone2_rname, + } + ), + expected=errors.NotFound(reason='Nameserver \'%s\' does not have a corresponding A/AAAA record' % (dnszone2_mname)), + ), + + dict( + desc='Create a zone with nonexistent NS entry with --force', + command=( + 'dnszone_add', [dnszone2], { + 'idnssoamname': dnszone2_mname, + 'idnssoarname': dnszone2_rname, + 'force' : True, + } + ), + expected={ + 'value': dnszone2, + 'summary': None, + 'result': { + 'dn': dnszone2_dn, + 'idnsname': [dnszone2], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone2_mname], + 'nsrecord': [dnszone2_mname], + 'idnssoarname': [dnszone2_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Retrieve zone %r' % dnszone1, + command=('dnszone_show', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }, + }, + ), + + + dict( + desc='Update zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnssoarefresh': 5478}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }, + }, + ), + + + dict( + desc='Try to create reverse zone %r with NS record in it' % revdnszone1, + command=( + 'dnszone_add', [revdnszone1], { + 'idnssoamname': u'ns', + 'idnssoarname': dnszone1_rname, + } + ), + expected=errors.ValidationError(name='name-server', + error=u"Nameserver for reverse zone cannot be a relative DNS name"), + ), + + + dict( + desc='Create reverse zone %r' % revdnszone1, + command=( + 'dnszone_add', [revdnszone1], { + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + } + ), + expected={ + 'value': revdnszone1, + 'summary': None, + 'result': { + 'dn': revdnszone1_dn, + 'idnsname': [revdnszone1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone1_mname], + 'nsrecord': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-subdomain %(zone)s PTR;' + % dict(realm=api.env.realm, zone=revdnszone1)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Search for zones with name server %r' % (dnszone1_mname), + command=('dnszone_find', [], {'idnssoamname': dnszone1_mname}), + expected={ + 'summary': None, + 'count': 2, + 'truncated': False, + 'result': [{ + 'dn': revdnszone1_dn, + 'idnsname': [revdnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }, + { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }], + }, + ), + + + dict( + desc='Search for zones with name server %r with --forward-only' % dnszone1_mname, + command=('dnszone_find', [], {'idnssoamname': dnszone1_mname, 'forward_only' : True}), + expected={ + 'summary': None, + 'count': 1, + 'truncated': False, + 'result': [{ + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }], + }, + ), + + + dict( + desc='Delete reverse zone %r' % revdnszone1, + command=('dnszone_del', [revdnszone1], {}), + expected={ + 'value': revdnszone1, + 'summary': u'Deleted DNS zone "%s"' % revdnszone1, + 'result': {'failed': u''}, + }, + ), + + + dict( + desc='Disable zone %r' % dnszone1, + command=('dnszone_disable', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': u'Disabled DNS zone "%s"' % dnszone1, + 'result': True, + }, + ), + + + dict( + desc='Check if zone %r is really disabled' % dnszone1, + command=('dnszone_show', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'FALSE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }, + }, + ), + + + dict( + desc='Enable zone %r' % dnszone1, + command=('dnszone_enable', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': u'Enabled DNS zone "%s"' % dnszone1, + 'result': True, + }, + ), + + + dict( + desc='Check if zone %r is really enabled' % dnszone1, + command=('dnszone_show', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + }, + }, + ), + + + dict( + desc='Try to retrieve non-existent record %r in zone %r' % (dnsres1, dnszone1), + command=('dnsrecord_show', [dnszone1, dnsres1], {}), + expected=errors.NotFound( + reason=u'%s: DNS resource record not found' % dnsres1), + ), + + + dict( + desc='Try to delete non-existent record %r in zone %r' % (dnsres1, dnszone1), + command=('dnsrecord_del', [dnszone1, dnsres1], {'del_all' : True}), + expected=errors.NotFound( + reason=u'%s: DNS resource record not found' % dnsres1), + ), + + + dict( + desc='Try to delete root zone record \'@\' in %r' % (dnszone1), + command=('dnsrecord_del', [dnszone1, u'@'], {'del_all' : True}), + expected=errors.ValidationError(name='del_all', + error=u"Zone record '@' cannot be deleted"), + ), + + + dict( + desc='Try to create record with invalid name in zone %r' % dnszone1, + command=('dnsrecord_add', [dnszone1, u'invalid record'], {'arecord': u'127.0.0.1'}), + expected=errors.ValidationError(name='name', + error=u'only letters, numbers, _, and - are allowed. ' + + u'DNS label may not start or end with -'), + ), + + + dict( + desc='Create record %r in zone %r' % (dnszone1, dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'127.0.0.1'}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'objectclass': objectclasses.dnsrecord, + 'arecord': [u'127.0.0.1'], + }, + }, + ), + + + dict( + desc='Search for all records in zone %r' % dnszone1, + command=('dnsrecord_find', [dnszone1], {}), + expected={ + 'summary': None, + 'count': 4, + 'truncated': False, + 'result': [ + { + 'dn': dnszone1_dn, + 'nsrecord': (dnszone1_mname,), + 'idnsname': [u'@'], + }, + { + 'dn': dnszone1_txtrec_dn, + 'txtrecord': [api.env.realm], + 'idnsname': [u'_kerberos'], + }, + { + 'dn': dnszone1_mname_dn, + 'idnsname': [u'ns1'], + 'arecord': [u'1.2.3.4'], + }, + { + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'127.0.0.1'], + }, + ], + }, + ), + + + dict( + desc='Add A record to %r in zone %r' % (dnsres1, dnszone1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'10.10.0.1'}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'127.0.0.1', u'10.10.0.1'], + 'objectclass': objectclasses.dnsrecord, + }, + }, + ), + + + dict( + desc='Remove A record from %r in zone %r' % (dnsres1, dnszone1), + command=('dnsrecord_del', [dnszone1, dnsres1], {'arecord': u'127.0.0.1'}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + }, + }, + ), + + + dict( + desc='Add AAAA record to %r in zone %r using dnsrecord_mod' % (dnsres1, dnszone1), + command=('dnsrecord_mod', [dnszone1, dnsres1], {'aaaarecord': u'::1'}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'aaaarecord': [u'::1'], + }, + }, + ), + + + dict( + desc='Try to modify nonexistent record in zone %r' % dnszone1, + command=('dnsrecord_mod', + [dnszone1, u'ghostname'], + {'aaaarecord': u'f001:baad::1'}), + expected=errors.NotFound( + reason=u'ghostname: DNS resource record not found'), + ), + + + dict( + desc='Modify AAAA record in %r in zone %r' % (dnsres1, dnszone1), + command=('dnsrecord_mod', [dnszone1, dnsres1], {'aaaarecord': u'ff02::1'}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'aaaarecord': [u'ff02::1'], + }, + }, + ), + + + dict( + desc='Remove AAAA record from %r in zone %r using dnsrecord_mod' % (dnsres1, dnszone1), + command=('dnsrecord_mod', [dnszone1, dnsres1], {'aaaarecord': u''}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + }, + }, + ), + + dict( + desc='Try to add invalid MX record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'@'], {'mxrecord': dnszone1_mname }), + expected=errors.ValidationError(name='mx_rec', + error=u'format must be specified as "PREFERENCE EXCHANGER" ' + + u' (see RFC 1035 for details)'), + ), + + dict( + desc='Add MX record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'@'], {'mxrecord': u"0 %s" % dnszone1_mname }), + expected={ + 'value': u'@', + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnszone, + 'dn': dnszone1_dn, + 'idnsname': [u'@'], + 'mxrecord': [u"0 %s" % dnszone1_mname], + 'nsrecord': [dnszone1_mname], + }, + }, + ), + + dict( + desc='Try to add invalid SRV record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'_foo._tcp'], {'srvrecord': dnszone1_mname}), + expected=errors.ValidationError(name='srv_rec', + error=u'format must be specified as "PRIORITY WEIGHT PORT TARGET" ' + + u' (see RFC 2782 for details)'), + ), + + dict( + desc='Try to add invalid SRV record via parts to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 0, + 'srv_part_weight' : 0, + 'srv_part_port' : 123, + 'srv_part_target' : u'foo bar'}), + expected=errors.ValidationError(name='srv_target', + error=u'invalid domain-name: only letters, numbers, _, and - ' + + u'are allowed. DNS label may not start or end with -'), + ), + + dict( + desc='Try to add SRV record to zone %r both via parts and a raw value' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 0, + 'srv_part_weight' : 0, + 'srv_part_port' : 123, + 'srv_part_target' : u'foo.bar.', + 'srvrecord': [u"1 100 1234 %s" \ + % dnszone1_mname]}), + expected=errors.ValidationError(name='srv_target', + error=u'Raw value of a DNS record was already set by ' + + u'"srv_rec" option'), + ), + + dict( + desc='Add SRV record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'_foo._tcp'], {'srvrecord': u"0 100 1234 %s" % dnszone1_mname}), + expected={ + 'value': u'_foo._tcp', + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': DN(('idnsname', u'_foo._tcp'), dnszone1_dn), + 'idnsname': [u'_foo._tcp'], + 'srvrecord': [u"0 100 1234 %s" % dnszone1_mname], + }, + }, + ), + + dict( + desc='Try to modify SRV record in zone %r without specifying modified value' % (dnszone1), + command=('dnsrecord_mod', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 1,}), + expected=errors.RequirementError(name='srvrecord'), + ), + + dict( + desc='Try to modify SRV record in zone %r with non-existent modified value' % (dnszone1), + command=('dnsrecord_mod', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 1, + 'srvrecord' : [u"0 100 1234 does.not.exist."] }), + expected=errors.AttrValueNotFound(attr='SRV record', + value=u'0 100 1234 does.not.exist.'), + ), + + dict( + desc='Try to modify SRV record in zone %r with invalid part value' % (dnszone1), + command=('dnsrecord_mod', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 100000, + 'srvrecord' : [u"0 100 1234 %s" % dnszone1_mname] }), + expected=errors.ValidationError(name='srv_priority', error=u'can be at most 65535'), + ), + + dict( + desc='Modify SRV record in zone %r using parts' % (dnszone1), + command=('dnsrecord_mod', [dnszone1, u'_foo._tcp'], {'srv_part_priority': 1, + 'srvrecord' : [u"0 100 1234 %s" % dnszone1_mname] }), + expected={ + 'value': u'_foo._tcp', + 'summary': None, + 'result': { + 'idnsname': [u'_foo._tcp'], + 'srvrecord': [u"1 100 1234 %s" % dnszone1_mname], + }, + }, + ), + + dict( + desc='Try to add invalid LOC record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"91 11 42.4 N 16 36 29.6 E 227.64" }), + expected=errors.ValidationError(name='lat_deg', + error=u'can be at most 90'), + ), + + dict( + desc='Add LOC record to zone %r using dnsrecord_add' % (dnszone1), + command=('dnsrecord_add', [dnszone1, u'@'], {'locrecord': u"49 11 42.4 N 16 36 29.6 E 227.64" }), + expected={ + 'value': u'@', + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnszone, + 'dn': dnszone1_dn, + 'idnsname': [u'@'], + 'mxrecord': [u"0 %s" % dnszone1_mname], + 'nsrecord': [dnszone1_mname], + 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"], + }, + }, + ), + + dict( + desc='Try to add CNAME record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'cnamerecord': u'foo-1.example.com.'}), + expected=errors.ValidationError(name='cnamerecord', + error=u'CNAME record is not allowed to coexist with any other ' + u'record (RFC 1034, section 3.6.2)'), + ), + + dict( + desc='Try to add invalid CNAME record %r using dnsrecord_add' % (dnsrescname), + command=('dnsrecord_add', [dnszone1, dnsrescname], {'cnamerecord': u'-.example.com'}), + expected=errors.ValidationError(name='hostname', + error=u'invalid domain-name: only letters, numbers, _, and - ' + + u'are allowed. DNS label may not start or end with -'), + ), + + dict( + desc='Try to add multiple CNAME record %r using dnsrecord_add' % (dnsrescname), + command=('dnsrecord_add', [dnszone1, dnsrescname], {'cnamerecord': + [u'1.example.com.', u'2.example.com.']}), + expected=errors.ValidationError(name='cnamerecord', + error=u'only one CNAME record is allowed per name (RFC 2136, section 1.1.5)'), + ), + + dict( + desc='Add CNAME record to %r using dnsrecord_add' % (dnsrescname), + command=('dnsrecord_add', [dnszone1, dnsrescname], {'cnamerecord': u'foo-1.example.com.'}), + expected={ + 'value': dnsrescname, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsrescname_dn, + 'idnsname': [dnsrescname], + 'cnamerecord': [u'foo-1.example.com.'], + }, + }, + ), + + dict( + desc='Try to add other record to CNAME record %r using dnsrecord_add' % (dnsrescname), + command=('dnsrecord_add', [dnszone1, dnsrescname], {'arecord': u'10.0.0.1'}), + expected=errors.ValidationError(name='cnamerecord', + error=u'CNAME record is not allowed to coexist with any other ' + u'record (RFC 1034, section 3.6.2)'), + ), + + dict( + desc='Try to add other record to CNAME record %r using dnsrecord_mod' % (dnsrescname), + command=('dnsrecord_mod', [dnszone1, dnsrescname], {'arecord': u'10.0.0.1'}), + expected=errors.ValidationError(name='cnamerecord', + error=u'CNAME record is not allowed to coexist with any other ' + u'record (RFC 1034, section 3.6.2)'), + ), + + dict( + desc='Add A record and delete CNAME record in %r with dnsrecord_mod' % (dnsrescname), + command=('dnsrecord_mod', [dnszone1, dnsrescname], {'arecord': u'10.0.0.1', + 'cnamerecord': None}), + expected={ + 'value': dnsrescname, + 'summary': None, + 'result': { + 'idnsname': [dnsrescname], + 'arecord': [u'10.0.0.1'], + }, + }, + ), + + dict( + desc='Try to add multiple DNAME records to %r using dnsrecord_add' % (dnsresdname), + command=('dnsrecord_add', [dnszone1, dnsres1], {'dnamerecord': + [u'foo-1.example.com.', u'foo-2.example.com.']}), + expected=errors.ValidationError(name='dnamerecord', + error=u'only one DNAME record is allowed per name (RFC 6672, section 2.4)'), + ), + + dict( + desc='Try to add invalid DNAME record %r using dnsrecord_add' % (dnsresdname), + command=('dnsrecord_add', [dnszone1, dnsresdname], {'dnamerecord': u'-.example.com.'}), + expected=errors.ValidationError(name='target', + error=u'invalid domain-name: only letters, numbers, _, and - ' + + u'are allowed. DNS label may not start or end with -'), + ), + + dict( + desc='Add DNAME record to %r using dnsrecord_add' % (dnsresdname), + command=('dnsrecord_add', [dnszone1, dnsresdname], + {'dnamerecord': u'd.example.com.', 'arecord': u'10.0.0.1'}), + expected={ + 'value': dnsresdname, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsresdname_dn, + 'idnsname': [dnsresdname], + 'dnamerecord': [u'd.example.com.'], + 'arecord': [u'10.0.0.1'], + }, + }, + ), + + dict( + desc='Try to add CNAME record to %r using dnsrecord_add' % (dnsresdname), + command=('dnsrecord_add', [dnszone1, dnsresdname], {'cnamerecord': u'foo-1.example.com.'}), + expected=errors.ValidationError(name='cnamerecord', + error=u'CNAME record is not allowed to coexist with any other ' + u'record (RFC 1034, section 3.6.2)'), + ), + + dict( + desc='Try to add NS record to %r using dnsrecord_add' % (dnsresdname), + command=('dnsrecord_add', [dnszone1, dnsresdname], + {'nsrecord': u'%s.%s.' % (dnsres1, dnszone1)}), + expected=errors.ValidationError(name='dnamerecord', + error=u'DNAME record is not allowed to coexist with an NS ' + u'record except when located in a zone root record (RFC 6672, section 2.3)'), + ), + + dict( + desc='Add NS+DNAME record to %r zone record using dnsrecord_add' % (dnszone2), + command=('dnsrecord_add', [dnszone2, u'@'], + {'dnamerecord': u'd.example.com.', + 'nsrecord': dnszone1_mname}), + expected = { + 'value': u'@', + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnszone, + 'dnamerecord': [u'd.example.com.'], + 'dn': dnszone2_dn, + 'nsrecord': [dnszone2_mname, dnszone1_mname], + 'idnsname': [u'@'] + } + }, + ), + + + dict( + desc='Delete zone %r' % dnszone2, + command=('dnszone_del', [dnszone2], {}), + expected={ + 'value': dnszone2, + 'summary': u'Deleted DNS zone "%s"' % dnszone2, + 'result': {'failed': u''}, + }, + ), + + dict( + desc='Try to add invalid KX record %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'kxrecord': u'foo-1.example.com' }), + expected=errors.ValidationError(name='kx_rec', + error=u'format must be specified as "PREFERENCE EXCHANGER" ' + + u' (see RFC 2230 for details)'), + ), + + dict( + desc='Add KX record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'kxrecord': u'1 foo-1' }), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'kxrecord': [u'1 foo-1'], + }, + }, + ), + + dict( + desc='Add TXT record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'txtrecord': u'foo bar' }), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'kxrecord': [u'1 foo-1'], + 'txtrecord': [u'foo bar'], + }, + }, + ), + + dict( + desc='Add NSEC record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], { + 'nsec_part_next': dnszone1, + 'nsec_part_types' : [u'TXT', u'A']}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'kxrecord': [u'1 foo-1'], + 'txtrecord': [u'foo bar'], + 'nsecrecord': [dnszone1 + u' TXT A'], + }, + }, + ), + + dict( + desc='Try to add unresolvable absolute NS record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'nsrecord': u'does.not.exist.'}), + expected=errors.NotFound(reason=u"Nameserver 'does.not.exist.' does not have a corresponding A/AAAA record"), + ), + + dict( + desc='Try to add unresolvable relative NS record to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'nsrecord': u'does.not.exist'}), + expected=errors.NotFound(reason=u"Nameserver 'does.not.exist.%s.' does not have a corresponding A/AAAA record" % dnszone1), + ), + + dict( + desc='Add unresolvable NS record with --force to %r using dnsrecord_add' % (dnsres1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'nsrecord': u'does.not.exist.', + 'force' : True}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'arecord': [u'10.10.0.1'], + 'kxrecord': [u'1 foo-1'], + 'txtrecord': [u'foo bar'], + 'nsecrecord': [dnszone1 + u' TXT A'], + 'nsrecord': [u'does.not.exist.'], + }, + }, + ), + + dict( + desc='Try to to rename DNS zone %r root record' % (dnszone1), + command=('dnsrecord_mod', [dnszone1, u'@'], {'rename': dnsres1_renamed,}), + expected=errors.ValidationError(name='rename', + error=u'DNS zone root record cannot be renamed') + ), + + dict( + desc='Rename DNS record %r to %r' % (dnsres1, dnsres1_renamed), + command=('dnsrecord_mod', [dnszone1, dnsres1], {'rename': dnsres1_renamed,}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'idnsname': [dnsres1_renamed], + 'arecord': [u'10.10.0.1'], + 'kxrecord': [u'1 foo-1'], + 'txtrecord': [u'foo bar'], + 'nsecrecord': [dnszone1 + u' TXT A'], + 'nsrecord': [u'does.not.exist.'], + }, + }, + ), + + + dict( + desc='Delete record %r in zone %r' % (dnsres1_renamed, dnszone1), + command=('dnsrecord_del', [dnszone1, dnsres1_renamed], {'del_all': True }), + expected={ + 'value': dnsres1_renamed, + 'summary': u'Deleted record "%s"' % dnsres1_renamed, + 'result': {'failed': u''}, + }, + ), + + + dict( + desc='Try to create a reverse zone from invalid IP', + command=( + 'dnszone_add', [], { + 'name_from_ip': u'foo', + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + } + ), + expected=errors.ValidationError(name='name_from_ip', + error=u'invalid IP network format'), + ), + + dict( + desc='Create reverse zone from IP/netmask %r using name_from_ip option' % revdnszone1_ip, + command=( + 'dnszone_add', [], { + 'name_from_ip': revdnszone1_ip, + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + } + ), + expected={ + 'value': revdnszone1, + 'summary': None, + 'result': { + 'dn': revdnszone1_dn, + 'idnsname': [revdnszone1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone1_mname], + 'nsrecord': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-subdomain %(zone)s PTR;' + % dict(realm=api.env.realm, zone=revdnszone1)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Create reverse zone from IP %r using name_from_ip option' % revdnszone2_ip, + command=( + 'dnszone_add', [], { + 'name_from_ip': revdnszone2_ip, + 'idnssoamname': dnszone1_mname, + 'idnssoarname': dnszone1_rname, + } + ), + expected={ + 'value': revdnszone2, + 'summary': None, + 'result': { + 'dn': revdnszone2_dn, + 'idnsname': [revdnszone2], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone1_mname], + 'nsrecord': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-subdomain %(zone)s PTR;' + % dict(realm=api.env.realm, zone=revdnszone2)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Try to add invalid PTR %r to %r using dnsrecord_add' % (dnsrev1, revdnszone1), + command=('dnsrecord_add', [revdnszone1, dnsrev1], {'ptrrecord': u'-.example.com' }), + expected=errors.ValidationError(name='hostname', + error=u'invalid domain-name: only letters, numbers, and - ' + + u'are allowed. DNS label may not start or end with -'), + ), + + dict( + desc='Add PTR record %r to %r using dnsrecord_add' % (dnsrev1, revdnszone1), + command=('dnsrecord_add', [revdnszone1, dnsrev1], {'ptrrecord': u'foo-1.example.com' }), + expected={ + 'value': dnsrev1, + 'summary': None, + 'result': { + 'objectclass': objectclasses.dnsrecord, + 'dn': dnsrev1_dn, + 'idnsname': [dnsrev1], + 'ptrrecord': [u'foo-1.example.com.'], + }, + }, + ), + + dict( + desc='Show record %r in zone %r with --structured and --all options'\ + % (dnsrev1, revdnszone1), + command=('dnsrecord_show', [revdnszone1, dnsrev1], + {'structured': True, 'all': True}), + expected={ + 'value': dnsrev1, + 'summary': None, + 'result': { + 'dn': dnsrev1_dn, + 'idnsname': [dnsrev1], + 'objectclass': objectclasses.dnsrecord, + 'dnsrecords': [ + { + 'dnstype': u'PTR', + 'dnsdata': u'foo-1.example.com.', + 'ptr_part_hostname': u'foo-1.example.com.' + }, + ], + }, + }, + ), + + dict( + desc='Update global DNS settings', + command=('dnsconfig_mod', [], {'idnsforwarders' : [u'80.142.15.80'],}), + expected={ + 'value': u'', + 'summary': None, + 'result': { + 'idnsforwarders': [u'80.142.15.80'], + }, + }, + ), + + + dict( + desc='Try to add invalid allow-query to zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnsallowquery': u'foo'}), + expected=errors.ValidationError(name='allow_query', + error=u"failed to detect a valid IP address from 'foo'"), + ), + + dict( + desc='Add allow-query ACL to zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnsallowquery': u'!10/8;any'}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'mxrecord': [u'0 ns1.dnszone.test.'], + 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowquery': [u'!10.0.0.0/8;any;'], + 'idnsallowtransfer': [u'none;'], + }, + }, + ), + + + dict( + desc='Try to add invalid allow-transfer to zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnsallowtransfer': u'10.'}), + expected=errors.ValidationError(name='allow_transfer', + error=u"failed to detect a valid IP address from '10.'"), + ), + + dict( + desc='Add allow-transer ACL to zone %r' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnsallowtransfer': u'80.142.15.80'}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'mxrecord': [u'0 ns1.dnszone.test.'], + 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowquery': [u'!10.0.0.0/8;any;'], + 'idnsallowtransfer': [u'80.142.15.80;'], + }, + }, + ), + + + dict( + desc='Set SOA serial of zone %r to high number' % dnszone1, + command=('dnszone_mod', [dnszone1], {'idnssoaserial': 4294967295}), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'nsrecord': [dnszone1_mname], + 'mxrecord': [u'0 ns1.dnszone.test.'], + 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"], + 'idnssoamname': [dnszone1_mname], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [u'4294967295'], + 'idnssoarefresh': [u'5478'], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowquery': [u'!10.0.0.0/8;any;'], + 'idnsallowtransfer': [u'80.142.15.80;'], + }, + }, + ), + + + dict( + desc='Try to create duplicate PTR record for %r with --a-create-reverse' % dnsres1, + command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'80.142.15.80', + 'a_extra_create_reverse' : True}), + expected=errors.DuplicateEntry(message=u'Reverse record for IP ' + + u'address 80.142.15.80 already exists in reverse zone ' + + u'15.142.80.in-addr.arpa..'), + ), + + + dict( + desc='Create A record %r in zone %r with --a-create-reverse' % (dnsres1, dnszone1), + command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'80.142.15.81', + 'a_extra_create_reverse' : True}), + expected={ + 'value': dnsres1, + 'summary': None, + 'result': { + 'dn': dnsres1_dn, + 'idnsname': [dnsres1], + 'objectclass': objectclasses.dnsrecord, + 'arecord': [u'80.142.15.81'], + }, + }, + ), + + + dict( + desc='Check reverse record for %r created via --a-create-reverse' % dnsres1, + command=('dnsrecord_show', [revdnszone1, dnsrev2], {}), + expected={ + 'value': dnsrev2, + 'summary': None, + 'result': { + 'dn': dnsrev2_dn, + 'idnsname': [dnsrev2], + 'ptrrecord': [dnsres1 + '.' + dnszone1 + '.'], + }, + }, + ), + + + dict( + desc='Try to add per-zone permission for unknown zone', + command=('dnszone_add_permission', [u'does.not.exist'], {}), + expected=errors.NotFound(reason=u'does.not.exist: DNS zone not found') + ), + + + dict( + desc='Add per-zone permission for zone %r' % dnszone1, + command=( + 'dnszone_add_permission', [dnszone1], {} + ), + expected=dict( + result=True, + value=dnszone1_permission, + summary=u'Added system permission "%s"' % dnszone1_permission, + ), + ), + + + dict( + desc='Try to add duplicate per-zone permission for zone %r' % dnszone1, + command=( + 'dnszone_add_permission', [dnszone1], {} + ), + expected=errors.DuplicateEntry(message=u'permission with name ' + '"%s" already exists' % dnszone1_permission) + ), + + + dict( + desc='Make sure the permission was created %r' % dnszone1, + command=( + 'permission_show', [dnszone1_permission], {} + ), + expected=dict( + value=dnszone1_permission, + summary=None, + result={ + 'dn': dnszone1_permission_dn, + 'cn': [dnszone1_permission], + 'ipapermissiontype': [u'SYSTEM'], + }, + ), + ), + + + dict( + desc='Try to remove per-zone permission for unknown zone', + command=('dnszone_remove_permission', [u'does.not.exist'], {}), + expected=errors.NotFound(reason=u'does.not.exist: DNS zone not found') + ), + + + dict( + desc='Remove per-zone permission for zone %r' % dnszone1, + command=( + 'dnszone_remove_permission', [dnszone1], {} + ), + expected=dict( + result=True, + value=dnszone1_permission, + summary=u'Removed system permission "%s"' % dnszone1_permission, + ), + ), + + + dict( + desc='Make sure the permission for zone %r was deleted' % dnszone1, + command=( + 'permission_show', [dnszone1_permission], {} + ), + expected=errors.NotFound(reason=u'%s: permission not found' + % dnszone1_permission) + ), + + + dict( + desc='Delete zone %r' % dnszone1, + command=('dnszone_del', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': u'Deleted DNS zone "%s"' % dnszone1, + 'result': {'failed': u''}, + }, + ), + + + dict( + desc='Try to create zone %r nameserver not in it' % dnszone1, + command=( + 'dnszone_add', [dnszone1], { + 'idnssoamname': u'not.in.this.zone.', + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected=errors.ValidationError(name='ip_address', + error=u"Nameserver DNS record is created only for nameservers" + u" in current zone"), + ), + + + dict( + desc='Create zone %r with relative nameserver' % dnszone1, + command=( + 'dnszone_add', [dnszone1], { + 'idnssoamname': u'ns', + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [u'ns'], + 'nsrecord': [u'ns'], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + + dict( + desc='Delete zone %r' % dnszone1, + command=('dnszone_del', [dnszone1], {}), + expected={ + 'value': dnszone1, + 'summary': u'Deleted DNS zone "%s"' % dnszone1, + 'result': {'failed': u''}, + }, + ), + + + dict( + desc='Create zone %r with nameserver in the zone itself' % dnszone1, + command=( + 'dnszone_add', [dnszone1], { + 'idnssoamname': dnszone1 + u'.', + 'idnssoarname': dnszone1_rname, + 'ip_address' : u'1.2.3.4', + } + ), + expected={ + 'value': dnszone1, + 'summary': None, + 'result': { + 'dn': dnszone1_dn, + 'idnsname': [dnszone1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [dnszone1 + u'.'], + 'nsrecord': [dnszone1 + u'.'], + 'idnssoarname': [dnszone1_rname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + }, + }, + ), + + ] diff --git a/ipatests/test_xmlrpc/test_dns_realmdomains_integration.py b/ipatests/test_xmlrpc/test_dns_realmdomains_integration.py new file mode 100644 index 000000000..1e46d362e --- /dev/null +++ b/ipatests/test_xmlrpc/test_dns_realmdomains_integration.py @@ -0,0 +1,168 @@ +# Authors: +# Ana Krivokapic <akrivoka@redhat.com> +# +# Copyright (C) 2013 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test integration of DNS and realmdomains. +1. dnszone_{add,del} should create/delete appropriate entry in realmdomains. +2. realmdomains_mod should add a _kerberos TXT record in the DNS zone. +""" + +from ipalib import api, errors +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits + + +cn = u'Realm Domains' +dn = DN(('cn', cn), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) +our_domain = api.env.domain +dnszone_1 = u'dnszone.test' +dnszone_1_dn = DN(('idnsname', dnszone_1), api.env.container_dns, + api.env.basedn) +idnssoamname = u'ns1.%s.' % dnszone_1 +idnssoarname = u'root.%s.' % dnszone_1 +dnszone_2 = u'dnszone2.test' +dnszone_2_dn = DN(('idnsname', dnszone_2), api.env.container_dns, + api.env.basedn) + + +def assert_realmdomain_and_txt_record_present(response): + zone = response['value'] + + r = api.Command['realmdomains_show']() + assert zone in r['result']['associateddomain'] + + r = api.Command['dnsrecord_show'](zone, u'_kerberos') + assert api.env.realm in r['result']['txtrecord'] + + return True + + +def assert_realmdomain_and_txt_record_not_present(response): + zone = response['value'] + + r = api.Command['realmdomains_show']() + assert zone not in r['result']['associateddomain'] + + try: + api.Command['dnsrecord_show'](zone, u'_kerberos') + except errors.NotFound: + return True + + +class test_dns_realmdomains_integration(Declarative): + cleanup_commands = [ + ('realmdomains_mod', [], {'associateddomain': [our_domain]}), + ('dnszone_del', [dnszone_1, dnszone_2], {'continue': True}), + ] + + tests = [ + dict( + desc='Check realmdomain and TXT record get created ' + 'during dnszone_add', + command=( + 'dnszone_add', [dnszone_1], { + 'idnssoamname': idnssoamname, + 'idnssoarname': idnssoarname, + 'ip_address': u'1.2.3.4', + } + ), + expected={ + 'value': dnszone_1, + 'summary': None, + 'result': { + 'dn': dnszone_1_dn, + 'idnsname': [dnszone_1], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [idnssoamname], + 'nsrecord': [idnssoamname], + 'idnssoarname': [idnssoarname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + + }, + }, + extra_check=assert_realmdomain_and_txt_record_present, + ), + + dict( + desc='Check realmdomain and TXT record do not get created ' + 'during dnszone_add for forwarded zone', + command=( + 'dnszone_add', [dnszone_2], { + 'idnssoamname': idnssoamname, + 'idnssoarname': idnssoarname, + 'idnsforwarders': u'1.2.3.4', + 'idnsforwardpolicy': u'only', + 'force': True, + } + ), + expected={ + 'value': dnszone_2, + 'summary': None, + 'result': { + 'dn': dnszone_2_dn, + 'idnsname': [dnszone_2], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [idnssoamname], + 'idnsforwarders': [u'1.2.3.4'], + 'idnsforwardpolicy': [u'only'], + 'nsrecord': [idnssoamname], + 'idnssoarname': [idnssoarname], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; ' + u'grant %(realm)s krb5-self * AAAA; ' + u'grant %(realm)s krb5-self * SSHFP;' + % dict(realm=api.env.realm)], + 'idnsallowtransfer': [u'none;'], + 'idnsallowquery': [u'any;'], + 'objectclass': objectclasses.dnszone, + + }, + }, + extra_check=assert_realmdomain_and_txt_record_not_present, + ), + + dict( + desc='Check realmdomain and TXT record get deleted ' + 'during dnszone_del', + command=('dnszone_del', [dnszone_1], {}), + expected={ + 'value': dnszone_1, + 'summary': u'Deleted DNS zone "%s"' % dnszone_1, + 'result': {'failed': u''}, + }, + extra_check=assert_realmdomain_and_txt_record_not_present, + ), + ] diff --git a/ipatests/test_xmlrpc/test_external_members.py b/ipatests/test_xmlrpc/test_external_members.py new file mode 100644 index 000000000..112470dcb --- /dev/null +++ b/ipatests/test_xmlrpc/test_external_members.py @@ -0,0 +1,160 @@ +# Authors: +# Ana Krivokapic <akrivoka@redhat.com> +# +# Copyright (C) 2013 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test adding/removing external members (trusted domain objects) to IPA groups. +These tests are skipped if trust is not established. +""" + +import nose +from ipalib import api +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_uuid, fuzzy_user_or_group_sid + +group_name = u'external_group' +group_desc = u'Test external group' +group_dn = DN(('cn', group_name), api.env.container_group, api.env.basedn) + + +def get_trusted_group_name(): + trusts = api.Command['trust_find']() + if trusts['count'] == 0: + return None + + ad_netbios = trusts['result'][0]['ipantflatname'] + return u'%s\Domain Admins' % ad_netbios + + +class test_external_members(Declarative): + @classmethod + def setUpClass(cls): + super(test_external_members, cls).setUpClass() + if not api.Backend.xmlclient.isconnected(): + api.Backend.xmlclient.connect(fallback=False) + + trusts = api.Command['trust_find']() + if trusts['count'] == 0: + raise nose.SkipTest('Trust is not established') + + cleanup_commands = [ + ('group_del', [group_name], {}), + ] + + tests = [ + dict( + desc='Create external group "%s"' % group_name, + command=( + 'group_add', [group_name], dict(description=group_desc, external=True) + ), + expected=dict( + value=group_name, + summary=u'Added group "%s"' % group_name, + result=dict( + cn=[group_name], + description=[group_desc], + objectclass=objectclasses.externalgroup, + ipauniqueid=[fuzzy_uuid], + dn=group_dn, + ), + ), + ), + dict( + desc='Add external member "%s" to group "%s"' % (get_trusted_group_name(), group_name), + command=( + 'group_add_member', [group_name], dict(ipaexternalmember=get_trusted_group_name()) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result=dict( + dn=group_dn, + ipaexternalmember=[fuzzy_user_or_group_sid], + cn=[group_name], + description=[group_desc], + ), + ), + ), + dict( + desc='Try to add duplicate external member "%s" to group "%s"' % (get_trusted_group_name(), group_name), + command=( + 'group_add_member', [group_name], dict(ipaexternalmember=get_trusted_group_name()) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + group=[(fuzzy_user_or_group_sid, u'This entry is already a member')], + user=tuple(), + ), + ), + result=dict( + dn=group_dn, + ipaexternalmember=[fuzzy_user_or_group_sid], + cn=[group_name], + description=[group_desc], + ), + ), + ), + dict( + desc='Remove external member "%s" from group "%s"' % (get_trusted_group_name(), group_name), + command=( + 'group_remove_member', [group_name], dict(ipaexternalmember=get_trusted_group_name()) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result=dict( + dn=group_dn, + cn=[group_name], + ipaexternalmember=[], + description=[group_desc], + ), + ), + ), + dict( + desc='Try to remove external entry "%s" which is not a member of group "%s" from group "%s"' % (get_trusted_group_name(), group_name, group_name), + command=( + 'group_remove_member', [group_name], dict(ipaexternalmember=get_trusted_group_name()) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + group=[(fuzzy_user_or_group_sid, u'This entry is not a member')], + user=tuple(), + ), + ), + result=dict( + dn=group_dn, + cn=[group_name], + description=[group_desc], + ), + ), + ), + ] diff --git a/ipatests/test_xmlrpc/test_group_plugin.py b/ipatests/test_xmlrpc/test_group_plugin.py new file mode 100644 index 000000000..1d0cfeb16 --- /dev/null +++ b/ipatests/test_xmlrpc/test_group_plugin.py @@ -0,0 +1,1046 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/group.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from ipatests.util import Fuzzy +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_set_ci +from ipapython.dn import DN + +group1 = u'testgroup1' +group2 = u'testgroup2' +group3 = u'testgroup3' +renamedgroup1 = u'testgroup' +user1 = u'tuser1' + +invalidgroup1=u'+tgroup1' + +# When adding external SID member to a group we can't test +# it fully due to possibly missing Samba 4 python bindings +# and/or not configured AD trusts. Thus, we'll use incorrect +# SID value to merely test that proper exceptions are raised +external_sid1=u'S-1-1-123456-789-1' + +def get_group_dn(cn): + return DN(('cn', cn), api.env.container_group, api.env.basedn) + +class test_group(Declarative): + cleanup_commands = [ + ('group_del', [group1], {}), + ('group_del', [group2], {}), + ('group_del', [group3], {}), + ('group_del', [renamedgroup1], {}), + ('user_del', [user1], {}), + ] + + tests = [ + + ################ + # create group1: + dict( + desc='Try to retrieve non-existent %r' % group1, + command=('group_show', [group1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Try to update non-existent %r' % group1, + command=('group_mod', [group1], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Try to delete non-existent %r' % group1, + command=('group_del', [group1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Try to rename non-existent %r' % group1, + command=('group_mod', [group1], dict(setattr=u'cn=%s' % renamedgroup1)), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Create non-POSIX %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc 1',nonposix=True) + ), + expected=dict( + value=group1, + summary=u'Added group "testgroup1"', + result=dict( + cn=[group1], + description=[u'Test desc 1'], + objectclass=objectclasses.group, + ipauniqueid=[fuzzy_uuid], + dn=get_group_dn('testgroup1'), + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc 1') + ), + expected=errors.DuplicateEntry( + message=u'group with name "%s" already exists' % group1), + ), + + + dict( + desc='Retrieve non-POSIX %r' % group1, + command=('group_show', [group1], {}), + expected=dict( + value=group1, + summary=None, + result=dict( + cn=[group1], + description=[u'Test desc 1'], + dn=get_group_dn('testgroup1'), + ), + ), + ), + + + dict( + desc='Updated non-POSIX %r' % group1, + command=( + 'group_mod', [group1], dict(description=u'New desc 1') + ), + expected=dict( + result=dict( + cn=[group1], + description=[u'New desc 1'], + ), + summary=u'Modified group "testgroup1"', + value=group1, + ), + ), + + + dict( + desc='Retrieve %r to verify update' % group1, + command=('group_show', [group1], {}), + expected=dict( + value=group1, + result=dict( + cn=[group1], + description=[u'New desc 1'], + dn=get_group_dn('testgroup1'), + ), + summary=None, + ), + ), + + + # FIXME: The return value is totally different here than from the above + # group_mod() test. I think that for all *_mod() commands we should + # just return the entry exactly as *_show() does. + dict( + desc='Updated %r to promote it to a POSIX group' % group1, + command=('group_mod', [group1], dict(posix=True)), + expected=dict( + result=dict( + cn=[group1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + ), + value=group1, + summary=u'Modified group "testgroup1"', + ), + ), + + + dict( + desc="Retrieve %r to verify it's a POSIX group" % group1, + command=('group_show', [group1], {}), + expected=dict( + value=group1, + result=dict( + cn=[group1], + description=(u'New desc 1',), + dn=get_group_dn('testgroup1'), + gidnumber=[fuzzy_digits], + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % group1, + command=('group_find', [], dict(cn=group1)), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + dn=get_group_dn(group1), + cn=[group1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + ), + ], + summary=u'1 group matched', + ), + ), + + + + ################ + # create group2: + dict( + desc='Try to retrieve non-existent %r' % group2, + command=('group_show', [group2], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + + dict( + desc='Try to update non-existent %r' % group2, + command=('group_mod', [group2], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + + dict( + desc='Try to delete non-existent %r' % group2, + command=('group_del', [group2], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + + dict( + desc='Create %r' % group2, + command=( + 'group_add', [group2], dict(description=u'Test desc 2') + ), + expected=dict( + value=group2, + summary=u'Added group "testgroup2"', + result=dict( + cn=[group2], + description=[u'Test desc 2'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.posixgroup, + ipauniqueid=[fuzzy_uuid], + dn=get_group_dn('testgroup2'), + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % group2, + command=( + 'group_add', [group2], dict(description=u'Test desc 2') + ), + expected=errors.DuplicateEntry( + message=u'group with name "%s" already exists' % group2), + ), + + + dict( + desc='Retrieve %r' % group2, + command=('group_show', [group2], {}), + expected=dict( + value=group2, + summary=None, + result=dict( + cn=[group2], + description=[u'Test desc 2'], + gidnumber=[fuzzy_digits], + dn=get_group_dn('testgroup2'), + ), + ), + ), + + + dict( + desc='Updated %r' % group2, + command=( + 'group_mod', [group2], dict(description=u'New desc 2') + ), + expected=dict( + result=dict( + cn=[group2], + gidnumber=[fuzzy_digits], + description=[u'New desc 2'], + ), + summary=u'Modified group "testgroup2"', + value=group2, + ), + ), + + + dict( + desc='Retrieve %r to verify update' % group2, + command=('group_show', [group2], {}), + expected=dict( + value=group2, + result=dict( + cn=[group2], + description=[u'New desc 2'], + gidnumber=[fuzzy_digits], + dn=get_group_dn('testgroup2'), + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % group2, + command=('group_find', [], dict(cn=group2)), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + dn=get_group_dn('testgroup2'), + cn=[group2], + description=[u'New desc 2'], + gidnumber=[fuzzy_digits], + ), + ], + summary=u'1 group matched', + ), + ), + + + dict( + desc='Search for all groups', + command=('group_find', [], {}), + expected=dict( + summary=u'6 groups matched', + count=6, + truncated=False, + result=[ + { + 'dn': get_group_dn('admins'), + 'member_user': [u'admin'], + 'gidnumber': [fuzzy_digits], + 'cn': [u'admins'], + 'description': [u'Account administrators group'], + }, + { + 'dn': get_group_dn('editors'), + 'gidnumber': [fuzzy_digits], + 'cn': [u'editors'], + 'description': [u'Limited admins who can edit other users'], + }, + { + 'dn': get_group_dn('ipausers'), + 'cn': [u'ipausers'], + 'description': [u'Default group for all users'], + }, + dict( + dn=get_group_dn(group1), + cn=[group1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + ), + dict( + dn=get_group_dn(group2), + cn=[group2], + description=[u'New desc 2'], + gidnumber=[fuzzy_digits], + ), + { + 'dn': get_group_dn('trust admins'), + 'member_user': [u'admin'], + 'cn': [u'trust admins'], + 'description': [u'Trusts administrators group'], + }, + ], + ), + ), + + dict( + desc='Search for non-POSIX groups', + command=('group_find', [], dict(nonposix=True, all=True)), + expected=dict( + summary=u'2 groups matched', + count=2, + truncated=False, + result=[ + { + 'dn': get_group_dn('ipausers'), + 'cn': [u'ipausers'], + 'description': [u'Default group for all users'], + 'objectclass': fuzzy_set_ci(objectclasses.group), + 'ipauniqueid': [fuzzy_uuid], + }, + { + 'dn': get_group_dn('trust admins'), + 'member_user': [u'admin'], + 'cn': [u'trust admins'], + 'description': [u'Trusts administrators group'], + 'objectclass': fuzzy_set_ci(objectclasses.group), + 'ipauniqueid': [fuzzy_uuid], + }, + ], + ), + ), + + dict( + desc='Search for non-POSIX groups with criteria filter', + command=('group_find', [u'users'], dict(nonposix=True, all=True)), + expected=dict( + summary=u'1 group matched', + count=1, + truncated=False, + result=[ + { + 'dn': get_group_dn('ipausers'), + 'cn': [u'ipausers'], + 'description': [u'Default group for all users'], + 'objectclass': fuzzy_set_ci(objectclasses.group), + 'ipauniqueid': [fuzzy_uuid], + }, + ], + ), + ), + + dict( + desc='Search for POSIX groups', + command=('group_find', [], dict(posix=True, all=True)), + expected=dict( + summary=u'4 groups matched', + count=4, + truncated=False, + result=[ + { + 'dn': get_group_dn('admins'), + 'member_user': [u'admin'], + 'gidnumber': [fuzzy_digits], + 'cn': [u'admins'], + 'description': [u'Account administrators group'], + 'objectclass': fuzzy_set_ci(objectclasses.posixgroup), + 'ipauniqueid': [fuzzy_uuid], + }, + { + 'dn': get_group_dn('editors'), + 'gidnumber': [fuzzy_digits], + 'cn': [u'editors'], + 'description': [u'Limited admins who can edit other users'], + 'objectclass': fuzzy_set_ci(objectclasses.posixgroup), + 'ipauniqueid': [fuzzy_uuid], + }, + dict( + dn=get_group_dn(group1), + cn=[group1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + objectclass=fuzzy_set_ci(objectclasses.posixgroup), + ipauniqueid=[fuzzy_uuid], + ), + dict( + dn=get_group_dn(group2), + cn=[group2], + description=[u'New desc 2'], + gidnumber=[fuzzy_digits], + objectclass=fuzzy_set_ci(objectclasses.posixgroup), + ipauniqueid=[fuzzy_uuid], + ), + ], + ), + ), + + + ############### + # test external SID members for group3: + dict( + desc='Create external %r' % group3, + command=( + 'group_add', [group3], dict(description=u'Test desc 3',external=True) + ), + expected=dict( + value=group3, + summary=u'Added group "testgroup3"', + result=dict( + cn=[group3], + description=[u'Test desc 3'], + objectclass=objectclasses.externalgroup, + ipauniqueid=[fuzzy_uuid], + dn=get_group_dn(group3), + ), + ), + ), + + dict( + desc='Search for external groups', + command=('group_find', [], dict(external=True, all=True)), + expected=dict( + summary=u'1 group matched', + count=1, + truncated=False, + result=[ + dict( + cn=[group3], + description=[u'Test desc 3'], + objectclass=fuzzy_set_ci(objectclasses.externalgroup), + ipauniqueid=[fuzzy_uuid], + dn=get_group_dn(group3), + ), + ], + ), + ), + + + dict( + desc='Convert posix group %r to support external membership' % (group2), + command=( + 'group_mod', [group2], dict(external=True) + ), + expected=errors.PosixGroupViolation(), + ), + + + dict( + desc='Convert external members group %r to posix' % (group3), + command=( + 'group_mod', [group3], dict(posix=True) + ), + expected=errors.ExternalGroupViolation(), + ), + + + dict( + desc='Add external member %r to %r' % (external_sid1, group3), + command=( + 'group_add_member', [group3], dict(ipaexternalmember=external_sid1) + ), + expected=lambda x, output: type(x) == errors.ValidationError or type(x) == errors.NotFound, + ), + + + dict( + desc='Remove group %r with external membership' % (group3), + command=('group_del', [group3], {}), + expected=dict( + result=dict(failed=u''), + value=group3, + summary=u'Deleted group "testgroup3"', + ), + ), + + + ############### + # member stuff: + dict( + desc='Add member %r to %r' % (group2, group1), + command=( + 'group_add_member', [group1], dict(group=group2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn(group1), + 'member_group': (group2,), + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + # FIXME: Shouldn't this raise a NotFound instead? + desc='Try to add non-existent member to %r' % group1, + command=( + 'group_add_member', [group1], dict(group=u'notfound') + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + group=[(u'notfound', u'no such entry')], + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn(group1), + 'member_group': (group2,), + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Remove member %r from %r' % (group2, group1), + command=('group_remove_member', + [group1], dict(group=group2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn(group1), + 'cn': [group1], + 'gidnumber': [fuzzy_digits], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + # FIXME: Shouldn't this raise a NotFound instead? + desc='Try to remove non-existent member from %r' % group1, + command=('group_remove_member', + [group1], dict(group=u'notfound') + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + group=[(u'notfound', u'This entry is not a member')], + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn(group1), + 'cn': [group1], + 'gidnumber': [fuzzy_digits], + 'description': [u'New desc 1'], + }, + ), + ), + + + dict( + desc='Rename %r' % group1, + command=('group_mod', [group1], dict(setattr=u'cn=%s' % renamedgroup1)), + expected=dict( + value=group1, + result=dict( + cn=[renamedgroup1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + ), + summary=u'Modified group "%s"' % group1 + ) + ), + + + dict( + desc='Rename %r back' % renamedgroup1, + command=('group_mod', [renamedgroup1], dict(setattr=u'cn=%s' % group1)), + expected=dict( + value=renamedgroup1, + result=dict( + cn=[group1], + description=[u'New desc 1'], + gidnumber=[fuzzy_digits], + ), + summary=u'Modified group "%s"' % renamedgroup1 + ) + ), + + + + ################ + # delete group1: + dict( + desc='Delete %r' % group1, + command=('group_del', [group1], {}), + expected=dict( + result=dict(failed=u''), + value=group1, + summary=u'Deleted group "testgroup1"', + ) + ), + + + dict( + desc='Try to delete non-existent %r' % group1, + command=('group_del', [group1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Try to retrieve non-existent %r' % group1, + command=('group_show', [group1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + dict( + desc='Try to update non-existent %r' % group1, + command=('group_mod', [group1], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: group not found' % group1), + ), + + + + ################ + # delete group2: + dict( + desc='Delete %r' % group2, + command=('group_del', [group2], {}), + expected=dict( + result=dict(failed=u''), + value=group2, + summary=u'Deleted group "testgroup2"', + ) + ), + + + dict( + desc='Try to delete non-existent %r' % group2, + command=('group_del', [group2], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + + dict( + desc='Try to retrieve non-existent %r' % group2, + command=('group_show', [group2], {}), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + + dict( + desc='Try to update non-existent %r' % group2, + command=('group_mod', [group2], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: group not found' % group2), + ), + + dict( + desc='Test an invalid group name %r' % invalidgroup1, + command=('group_add', [invalidgroup1], dict(description=u'Test')), + expected=errors.ValidationError(name='group_name', + error=u'may only include letters, numbers, _, -, . and $'), + ), + + # The assumption on these next 4 tests is that if we don't get a + # validation error then the request was processed normally. + dict( + desc='Test that validation is disabled on mods', + command=('group_mod', [invalidgroup1], {}), + expected=errors.NotFound( + reason=u'%s: group not found' % invalidgroup1), + ), + + + dict( + desc='Test that validation is disabled on deletes', + command=('group_del', [invalidgroup1], {}), + expected=errors.NotFound( + reason=u'%s: group not found' % invalidgroup1), + ), + + + dict( + desc='Test that validation is disabled on show', + command=('group_show', [invalidgroup1], {}), + expected=errors.NotFound( + reason=u'%s: group not found' % invalidgroup1), + ), + + + ##### managed entry tests + dict( + desc='Create %r' % user1, + command=( + 'user_add', [], dict(givenname=u'Test', sn=u'User1') + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/home/%s' % user1], + krbprincipalname=[u'%s@%s' % (user1, api.env.realm)], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + dn=DN(('uid',user1),('cn','users'),('cn','accounts'), + api.env.basedn), + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Verify the managed group %r was created' % user1, + command=('group_show', [user1], {}), + expected=dict( + value=user1, + summary=None, + result=dict( + cn=[user1], + description=[u'User private group for %s' % user1], + gidnumber=[fuzzy_digits], + dn=get_group_dn(user1), + ), + ), + ), + + + dict( + desc='Verify that managed group %r can be found' % user1, + command=('group_find', [], {'cn': user1, 'private': True}), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + dn=get_group_dn(user1), + cn=[user1], + description=[u'User private group for %s' % user1], + gidnumber=[fuzzy_digits], + ), + ], + summary=u'1 group matched', + ), + ), + + + dict( + desc='Try to delete a managed group %r' % user1, + command=('group_del', [user1], {}), + expected=errors.ManagedGroupError(), + ), + + + dict( + desc='Detach managed group %r' % user1, + command=('group_detach', [user1], {}), + expected=dict( + result=True, + value=user1, + summary=u'Detached group "%s" from user "%s"' % (user1, user1), + ), + ), + + + dict( + desc='Now delete the unmanaged group %r' % user1, + command=('group_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + value=user1, + summary=u'Deleted group "%s"' % user1, + ) + ), + + dict( + desc='Verify that %r is really gone' % user1, + command=('group_show', [user1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % user1), + ), + + dict( + desc='Delete %r' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "tuser1"', + value=user1, + ), + ), + + dict( + desc='Create %r without User Private Group' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', noprivate=True, gidnumber=1000) + ), + expected=dict( + value=user1, + summary=u'Added user "tuser1"', + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + description=[], + homedirectory=[u'/home/tuser1'], + krbprincipalname=[u'tuser1@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[u'1000'], + mail=[u'%s@%s' % (user1, api.env.domain)], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('uid','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + ), + ), + ), + + dict( + desc='Verify the managed group %r was not created' % user1, + command=('group_show', [user1], {}), + expected=errors.NotFound(reason=u'%s: group not found' % user1), + ), + + dict( + desc='Try to remove the admin user from the admins group', + command=('group_remove_member', [u'admins'], dict(user=[u'admin'])), + expected=errors.LastMemberError(key=u'admin', label=u'group', + container='admins'), + ), + + dict( + desc='Add %r to the admins group' % user1, + command=('group_add_member', [u'admins'], dict(user=user1)), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn('admins'), + 'member_user': [u'admin', user1], + 'gidnumber': [fuzzy_digits], + 'cn': [u'admins'], + 'description': [u'Account administrators group'], + }, + ), + ), + + dict( + desc='Try to remove admin and %r from the admins group' % user1, + command=('group_remove_member', [u'admins'], + dict(user=[u'admin', user1])), + expected=errors.LastMemberError(key=u'admin', label=u'group', + container='admins'), + ), + + dict( + desc='Try to delete the admins group', + command=('group_del', [u'admins'], {}), + expected=errors.ProtectedEntryError(label=u'group', + key='admins', reason='privileged group'), + ), + + + dict( + desc='Try to rename the admins group', + command=('group_mod', [u'admins'], dict(rename=u'loosers')), + expected=errors.ProtectedEntryError(label=u'group', + key='admins', reason='Cannot be renamed'), + ), + + dict( + desc='Try to rename the admins group via setattr', + command=('group_mod', [u'admins'], {'setattr': u'cn=loosers'}), + expected=errors.ProtectedEntryError(label=u'group', + key='admins', reason='Cannot be renamed'), + ), + + dict( + desc='Try to modify the admins group to support external membership', + command=('group_mod', [u'admins'], dict(external=True)), + expected=errors.ProtectedEntryError(label=u'group', + key='admins', reason='Cannot support external non-IPA members'), + ), + + dict( + desc='Try to delete the trust admins group', + command=('group_del', [u'trust admins'], {}), + expected=errors.ProtectedEntryError(label=u'group', + key='trust admins', reason='privileged group'), + ), + + dict( + desc='Try to rename the trust admins group', + command=('group_mod', [u'trust admins'], dict(rename=u'loosers')), + expected=errors.ProtectedEntryError(label=u'group', + key='trust admins', reason='Cannot be renamed'), + ), + + dict( + desc='Try to rename the trust admins group via setattr', + command=('group_mod', [u'trust admins'], {'setattr': u'cn=loosers'}), + expected=errors.ProtectedEntryError(label=u'group', + key='trust admins', reason='Cannot be renamed'), + ), + + + dict( + desc='Try to modify the trust admins group to support external membership', + command=('group_mod', [u'trust admins'], dict(external=True)), + expected=errors.ProtectedEntryError(label=u'group', + key='trust admins', reason='Cannot support external non-IPA members'), + ), + + dict( + desc='Delete %r' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_hbac_plugin.py b/ipatests/test_xmlrpc/test_hbac_plugin.py new file mode 100644 index 000000000..c0f8b5307 --- /dev/null +++ b/ipatests/test_xmlrpc/test_hbac_plugin.py @@ -0,0 +1,497 @@ +# Authors: +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/hbacrule.py` module. +""" + +from nose.tools import raises, assert_raises # pylint: disable=E0611 + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors + +class test_hbac(XMLRPC_test): + """ + Test the `hbacrule` plugin. + """ + rule_name = u'testing_rule1234' + rule_type = u'allow' + rule_type_fail = u'value not allowed' + rule_service = u'ssh' + rule_time = u'absolute 20081010000000 ~ 20081015120000' + rule_time2 = u'absolute 20081010000000 ~ 20081016120000' + # wrong time, has 30th day in February in first date + rule_time_fail = u'absolute 20080230000000 ~ 20081015120000' + rule_desc = u'description' + rule_desc_mod = u'description modified' + + test_user = u'hbacrule_test_user' + test_group = u'hbacrule_test_group' + test_host = u'hbacrule.testnetgroup' + test_hostgroup = u'hbacrule_test_hostgroup' + test_service = u'sshd' + test_host_external = u'notfound.example.com' + + test_invalid_sourcehost = u'inv+alid#srchost.nonexist.com' + + def test_0_hbacrule_add(self): + """ + Test adding a new HBAC rule using `xmlrpc.hbacrule_add`. + """ + ret = self.failsafe_add(api.Object.hbacrule, + self.rule_name, + accessruletype=self.rule_type, + description=self.rule_desc, + ) + entry = ret['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'accessruletype', self.rule_type) + assert_attr_equal(entry, 'ipaenabledflag', 'TRUE') + assert_attr_equal(entry, 'description', self.rule_desc) + + @raises(errors.DuplicateEntry) + def test_1_hbacrule_add(self): + """ + Test adding an existing HBAC rule using `xmlrpc.hbacrule_add'. + """ + api.Command['hbacrule_add']( + self.rule_name, accessruletype=self.rule_type + ) + + def test_2_hbacrule_show(self): + """ + Test displaying a HBAC rule using `xmlrpc.hbacrule_show`. + """ + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'ipaenabledflag', 'TRUE') + assert_attr_equal(entry, 'description', self.rule_desc) + + def test_3_hbacrule_mod(self): + """ + Test modifying a HBAC rule using `xmlrpc.hbacrule_mod`. + """ + ret = api.Command['hbacrule_mod']( + self.rule_name, description=self.rule_desc_mod + ) + entry = ret['result'] + assert_attr_equal(entry, 'description', self.rule_desc_mod) + +# def test_4_hbacrule_add_accesstime(self): +# """ +# Test adding access time to HBAC rule using `xmlrpc.hbacrule_add_accesstime`. +# """ +# return +# ret = api.Command['hbacrule_add_accesstime']( +# self.rule_name, accesstime=self.rule_time2 +# ) +# entry = ret['result'] +# assert_attr_equal(entry, 'accesstime', self.rule_time); +# assert_attr_equal(entry, 'accesstime', self.rule_time2); + +# def test_5_hbacrule_add_accesstime(self): +# """ +# Test adding invalid access time to HBAC rule using `xmlrpc.hbacrule_add_accesstime`. +# """ +# try: +# api.Command['hbacrule_add_accesstime']( +# self.rule_name, accesstime=self.rule_time_fail +# ) +# except errors.ValidationError: +# pass +# else: +# assert False + + def test_6_hbacrule_find(self): + """ + Test searching for HBAC rules using `xmlrpc.hbacrule_find`. + """ + ret = api.Command['hbacrule_find']( + cn=self.rule_name, accessruletype=self.rule_type, + description=self.rule_desc_mod + ) + assert ret['truncated'] is False + entries = ret['result'] + assert_attr_equal(entries[0], 'cn', self.rule_name) + assert_attr_equal(entries[0], 'accessruletype', self.rule_type) + assert_attr_equal(entries[0], 'description', self.rule_desc_mod) + + def test_7_hbacrule_init_testing_data(self): + """ + Initialize data for more HBAC plugin testing. + """ + self.failsafe_add(api.Object.user, + self.test_user, givenname=u'first', sn=u'last' + ) + self.failsafe_add(api.Object.group, + self.test_group, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_host, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_hostgroup, description=u'description' + ) + self.failsafe_add(api.Object.hbacsvc, + self.test_service, description=u'desc', + ) + + def test_8_hbacrule_add_user(self): + """ + Test adding user and group to HBAC rule using `xmlrpc.hbacrule_add_user`. + """ + ret = api.Command['hbacrule_add_user']( + self.rule_name, user=self.test_user, group=self.test_group + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert not failed['memberuser']['user'] + assert 'group' in failed['memberuser'] + assert not failed['memberuser']['group'] + entry = ret['result'] + assert_attr_equal(entry, 'memberuser_user', self.test_user) + assert_attr_equal(entry, 'memberuser_group', self.test_group) + + def test_9_a_show_user(self): + """ + Test showing a user to verify HBAC rule membership + `xmlrpc.user_show`. + """ + ret = api.Command['user_show'](self.test_user, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_hbacrule', self.rule_name) + + def test_9_b_show_group(self): + """ + Test showing a group to verify HBAC rule membership + `xmlrpc.group_show`. + """ + ret = api.Command['group_show'](self.test_group, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_hbacrule', self.rule_name) + + def test_9_hbacrule_remove_user(self): + """ + Test removing user and group from HBAC rule using `xmlrpc.hbacrule_remove_user'. + """ + ret = api.Command['hbacrule_remove_user']( + self.rule_name, user=self.test_user, group=self.test_group + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert not failed['memberuser']['user'] + assert 'group' in failed['memberuser'] + assert not failed['memberuser']['group'] + entry = ret['result'] + assert 'memberuser_user' not in entry + assert 'memberuser_group' not in entry + + def test_a_hbacrule_add_host(self): + """ + Test adding host and hostgroup to HBAC rule using `xmlrpc.hbacrule_add_host`. + """ + ret = api.Command['hbacrule_add_host']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert not failed['memberhost']['host'] + assert 'hostgroup' in failed['memberhost'] + assert not failed['memberhost']['hostgroup'] + entry = ret['result'] + assert_attr_equal(entry, 'memberhost_host', self.test_host) + assert_attr_equal(entry, 'memberhost_hostgroup', self.test_hostgroup) + + def test_a_hbacrule_show_host(self): + """ + Test showing host to verify HBAC rule membership + `xmlrpc.host_show`. + """ + ret = api.Command['host_show'](self.test_host, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_hbacrule', self.rule_name) + + def test_a_hbacrule_show_hostgroup(self): + """ + Test showing hostgroup to verify HBAC rule membership + `xmlrpc.hostgroup_show`. + """ + ret = api.Command['hostgroup_show'](self.test_hostgroup, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_hbacrule', self.rule_name) + + def test_b_hbacrule_remove_host(self): + """ + Test removing host and hostgroup from HBAC rule using `xmlrpc.hbacrule_remove_host`. + """ + ret = api.Command['hbacrule_remove_host']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert not failed['memberhost']['host'] + assert 'hostgroup' in failed['memberhost'] + assert not failed['memberhost']['hostgroup'] + entry = ret['result'] + assert 'memberhost_host' not in entry + assert 'memberhost_hostgroup' not in entry + + @raises(errors.DeprecationError) + def test_a_hbacrule_add_sourcehost_deprecated(self): + """ + Test deprecated command hbacrule_add_sourcehost. + """ + ret = api.Command['hbacrule_add_sourcehost']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + + def test_a_hbacrule_add_service(self): + """ + Test adding service to HBAC rule using `xmlrpc.hbacrule_add_service`. + """ + ret = api.Command['hbacrule_add_service']( + self.rule_name, hbacsvc=self.test_service + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'memberservice' in failed + assert 'hbacsvc' in failed['memberservice'] + assert not failed['memberservice']['hbacsvc'] + entry = ret['result'] + assert_attr_equal(entry, 'memberservice_hbacsvc', self.test_service) + + def test_a_hbacrule_remove_service(self): + """ + Test removing service to HBAC rule using `xmlrpc.hbacrule_remove_service`. + """ + ret = api.Command['hbacrule_remove_service']( + self.rule_name, hbacsvc=self.test_service + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'memberservice' in failed + assert 'hbacsvc' in failed['memberservice'] + assert not failed['memberservice']['hbacsvc'] + entry = ret['result'] + assert 'memberservice service' not in entry + + @raises(errors.DeprecationError) + def test_b_hbacrule_remove_sourcehost_deprecated(self): + """ + Test deprecated command hbacrule_remove_sourcehost. + """ + ret = api.Command['hbacrule_remove_sourcehost']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + + @raises(errors.ValidationError) + def test_c_hbacrule_mod_invalid_external_setattr(self): + """ + Test adding the same external host using `xmlrpc.hbacrule_add_host`. + """ + ret = api.Command['hbacrule_mod']( + self.rule_name, setattr=self.test_invalid_sourcehost + ) + + def test_d_hbacrule_disable(self): + """ + Test disabling HBAC rule using `xmlrpc.hbacrule_disable`. + """ + assert api.Command['hbacrule_disable'](self.rule_name)['result'] is True + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + # FIXME: Should this be 'disabled' or 'FALSE'? + assert_attr_equal(entry, 'ipaenabledflag', 'FALSE') + + def test_e_hbacrule_enabled(self): + """ + Test enabling HBAC rule using `xmlrpc.hbacrule_enable`. + """ + assert api.Command['hbacrule_enable'](self.rule_name)['result'] is True + # check it's really enabled + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + # FIXME: Should this be 'enabled' or 'TRUE'? + assert_attr_equal(entry, 'ipaenabledflag', 'TRUE') + + def test_ea_hbacrule_disable_setattr(self): + """ + Test disabling HBAC rule using setattr + """ + command_result = api.Command['hbacrule_mod']( + self.rule_name, setattr=u'ipaenabledflag=false') + assert command_result['result']['ipaenabledflag'] == (u'FALSE',) + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'ipaenabledflag', 'FALSE') + + def test_eb_hbacrule_enable_setattr(self): + """ + Test enabling HBAC rule using setattr + """ + command_result = api.Command['hbacrule_mod']( + self.rule_name, setattr=u'ipaenabledflag=1') + assert command_result['result']['ipaenabledflag'] == (u'TRUE',) + # check it's really enabled + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'ipaenabledflag', 'TRUE') + + @raises(errors.MutuallyExclusiveError) + def test_f_hbacrule_exclusiveuser(self): + """ + Test adding a user to an HBAC rule when usercat='all' + """ + api.Command['hbacrule_mod'](self.rule_name, usercategory=u'all') + try: + api.Command['hbacrule_add_user'](self.rule_name, user=u'admin') + finally: + api.Command['hbacrule_mod'](self.rule_name, usercategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_g_hbacrule_exclusiveuser(self): + """ + Test setting usercat='all' in an HBAC rule when there are users + """ + api.Command['hbacrule_add_user'](self.rule_name, user=u'admin') + try: + api.Command['hbacrule_mod'](self.rule_name, usercategory=u'all') + finally: + api.Command['hbacrule_remove_user'](self.rule_name, user=u'admin') + + @raises(errors.MutuallyExclusiveError) + def test_h_hbacrule_exclusivehost(self): + """ + Test adding a host to an HBAC rule when hostcat='all' + """ + api.Command['hbacrule_mod'](self.rule_name, hostcategory=u'all') + try: + api.Command['hbacrule_add_host'](self.rule_name, host=self.test_host) + finally: + api.Command['hbacrule_mod'](self.rule_name, hostcategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_i_hbacrule_exclusivehost(self): + """ + Test setting hostcat='all' in an HBAC rule when there are hosts + """ + api.Command['hbacrule_add_host'](self.rule_name, host=self.test_host) + try: + api.Command['hbacrule_mod'](self.rule_name, hostcategory=u'all') + finally: + api.Command['hbacrule_remove_host'](self.rule_name, host=self.test_host) + + @raises(errors.MutuallyExclusiveError) + def test_j_hbacrule_exclusiveservice(self): + """ + Test adding a service to an HBAC rule when servicecat='all' + """ + api.Command['hbacrule_mod'](self.rule_name, servicecategory=u'all') + try: + api.Command['hbacrule_add_service'](self.rule_name, hbacsvc=self.test_service) + finally: + api.Command['hbacrule_mod'](self.rule_name, servicecategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_k_hbacrule_exclusiveservice(self): + """ + Test setting servicecat='all' in an HBAC rule when there are services + """ + api.Command['hbacrule_add_service'](self.rule_name, hbacsvc=self.test_service) + try: + api.Command['hbacrule_mod'](self.rule_name, servicecategory=u'all') + finally: + api.Command['hbacrule_remove_service'](self.rule_name, hbacsvc=self.test_service) + + @raises(errors.ValidationError) + def test_l_hbacrule_add(self): + """ + Test adding a new HBAC rule with a deny type. + """ + api.Command['hbacrule_add']( + u'denyrule', + accessruletype=u'deny', + description=self.rule_desc, + ) + + @raises(errors.ValidationError) + def test_m_hbacrule_add(self): + """ + Test changing an HBAC rule to the deny type + """ + api.Command['hbacrule_mod']( + self.rule_name, + accessruletype=u'deny', + ) + + def test_n_hbacrule_links(self): + """ + Test adding various links to HBAC rule + """ + api.Command['hbacrule_add_service']( + self.rule_name, hbacsvc=self.test_service + ) + + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'memberservice_hbacsvc', self.test_service) + + def test_y_hbacrule_zap_testing_data(self): + """ + Clear data for HBAC plugin testing. + """ + api.Command['hbacrule_remove_host'](self.rule_name, host=self.test_host) + api.Command['hbacrule_remove_host'](self.rule_name, hostgroup=self.test_hostgroup) + api.Command['user_del'](self.test_user) + api.Command['group_del'](self.test_group) + api.Command['host_del'](self.test_host) + api.Command['hostgroup_del'](self.test_hostgroup) + api.Command['hbacsvc_del'](self.test_service) + + def test_k_2_sudorule_referential_integrity(self): + """ + Test that links in HBAC rule were removed by referential integrity plugin + """ + entry = api.Command['hbacrule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert 'sourcehost_host' not in entry + assert 'sourcehost_hostgroup' not in entry + assert 'memberservice_hbacsvc' not in entry + + def test_z_hbacrule_del(self): + """ + Test deleting a HBAC rule using `xmlrpc.hbacrule_del`. + """ + api.Command['hbacrule_del'](self.rule_name) + # verify that it's gone + with assert_raises(errors.NotFound): + api.Command['hbacrule_show'](self.rule_name) + + @raises(errors.ValidationError) + def test_zz_hbacrule_add_with_deprecated_option(self): + """ + Test using a deprecated command option 'sourcehostcategory' with 'hbacrule_add'. + """ + api.Command['hbacrule_add']( + self.rule_name, sourcehostcategory=u'all' + ) diff --git a/ipatests/test_xmlrpc/test_hbacsvcgroup_plugin.py b/ipatests/test_xmlrpc/test_hbacsvcgroup_plugin.py new file mode 100644 index 000000000..8140741d9 --- /dev/null +++ b/ipatests/test_xmlrpc/test_hbacsvcgroup_plugin.py @@ -0,0 +1,256 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib.plugins.hbacsvcgroup` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid +from ipatests.test_xmlrpc import objectclasses +from ipapython.dn import DN + +hbacsvcgroup1 = u'testhbacsvcgroup1' +dn1 = DN(('cn',hbacsvcgroup1),('cn','hbacservicegroups'),('cn','hbac'), + api.env.basedn) + +hbacsvc1 = u'sshd' +hbacsvc_dn1 = DN(('cn',hbacsvc1),('cn','hbacservices'),('cn','hbac'), + api.env.basedn) + + +class test_hbacsvcgroup(Declarative): + + cleanup_commands = [ + ('hbacsvcgroup_del', [hbacsvcgroup1], {}), + ('hbacsvc_del', [hbacsvc1], {}), + ] + + tests=[ + + dict( + desc='Try to retrieve non-existent %r' % hbacsvcgroup1, + command=('hbacsvcgroup_show', [hbacsvcgroup1], {}), + expected=errors.NotFound( + reason=u'%s: HBAC service group not found' % hbacsvcgroup1), + ), + + + dict( + desc='Try to update non-existent %r' % hbacsvcgroup1, + command=('hbacsvcgroup_mod', [hbacsvcgroup1], + dict(description=u'Updated hbacsvcgroup 1') + ), + expected=errors.NotFound( + reason=u'%s: HBAC service group not found' % hbacsvcgroup1), + ), + + + dict( + desc='Try to delete non-existent %r' % hbacsvcgroup1, + command=('hbacsvcgroup_del', [hbacsvcgroup1], {}), + expected=errors.NotFound( + reason=u'%s: HBAC service group not found' % hbacsvcgroup1), + ), + + + dict( + desc='Create %r' % hbacsvcgroup1, + command=('hbacsvcgroup_add', [hbacsvcgroup1], + dict(description=u'Test hbacsvcgroup 1') + ), + expected=dict( + value=hbacsvcgroup1, + summary=u'Added HBAC service group "testhbacsvcgroup1"', + result=dict( + dn=dn1, + cn=[hbacsvcgroup1], + objectclass=objectclasses.hbacsvcgroup, + description=[u'Test hbacsvcgroup 1'], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % hbacsvcgroup1, + command=('hbacsvcgroup_add', [hbacsvcgroup1], + dict(description=u'Test hbacsvcgroup 1') + ), + expected=errors.DuplicateEntry( + message=u'HBAC service group with name "%s" already exists' % + hbacsvcgroup1), + ), + + + dict( + desc='Create service %r' % hbacsvc1, + command=('hbacsvc_add', [hbacsvc1], + dict( + description=u'Test service 1', + ), + ), + expected=dict( + value=hbacsvc1, + summary=u'Added HBAC service "%s"' % hbacsvc1, + result=dict( + dn=hbacsvc_dn1, + cn=[hbacsvc1], + description=[u'Test service 1'], + objectclass=objectclasses.hbacsvc, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc=u'Add service %r to %r' % (hbacsvc1, hbacsvcgroup1), + command=( + 'hbacsvcgroup_add_member', [hbacsvcgroup1], dict(hbacsvc=hbacsvc1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + hbacsvc=tuple(), + ), + ), + result={ + 'dn': dn1, + 'cn': [hbacsvcgroup1], + 'description': [u'Test hbacsvcgroup 1'], + 'member_hbacsvc': [hbacsvc1], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hbacsvcgroup1, + command=('hbacsvcgroup_show', [hbacsvcgroup1], {}), + expected=dict( + value=hbacsvcgroup1, + summary=None, + result={ + 'dn': dn1, + 'member_hbacsvc': [hbacsvc1], + 'cn': [hbacsvcgroup1], + 'description': [u'Test hbacsvcgroup 1'], + }, + ), + ), + + + dict( + desc='Search for %r' % hbacsvcgroup1, + command=('hbacsvcgroup_find', [], dict(cn=hbacsvcgroup1)), + expected=dict( + count=1, + truncated=False, + summary=u'1 HBAC service group matched', + result=[ + { + 'dn': dn1, + 'member_hbacsvc': [hbacsvc1], + 'cn': [hbacsvcgroup1], + 'description': [u'Test hbacsvcgroup 1'], + }, + ], + ), + ), + + + dict( + desc='Update %r' % hbacsvcgroup1, + command=('hbacsvcgroup_mod', [hbacsvcgroup1], + dict(description=u'Updated hbacsvcgroup 1') + ), + expected=dict( + value=hbacsvcgroup1, + summary=u'Modified HBAC service group "testhbacsvcgroup1"', + result=dict( + cn=[hbacsvcgroup1], + description=[u'Updated hbacsvcgroup 1'], + member_hbacsvc=[hbacsvc1], + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % hbacsvcgroup1, + command=('hbacsvcgroup_show', [hbacsvcgroup1], {}), + expected=dict( + value=hbacsvcgroup1, + summary=None, + result={ + 'dn': dn1, + 'member_hbacsvc': [hbacsvc1], + 'cn': [hbacsvcgroup1], + 'description': [u'Updated hbacsvcgroup 1'], + }, + ), + ), + + + dict( + desc='Remove service %r from %r' % (hbacsvc1, hbacsvcgroup1), + command=('hbacsvcgroup_remove_member', [hbacsvcgroup1], + dict(hbacsvc=hbacsvc1) + ), + expected=dict( + failed=dict( + member=dict( + hbacsvc=tuple(), + ), + ), + completed=1, + result={ + 'dn': dn1, + 'cn': [hbacsvcgroup1], + 'description': [u'Updated hbacsvcgroup 1'], + }, + ), + ), + + + dict( + desc='Delete %r' % hbacsvcgroup1, + command=('hbacsvcgroup_del', [hbacsvcgroup1], {}), + expected=dict( + value=hbacsvcgroup1, + summary=u'Deleted HBAC service group "testhbacsvcgroup1"', + result=dict(failed=u''), + ), + ), + + + dict( + desc='Delete service %r' % hbacsvc1, + command=('hbacsvc_del', [hbacsvc1], {}), + expected=dict( + value=hbacsvc1, + summary=u'Deleted HBAC service "%s"' % hbacsvc1, + result=dict(failed=u''), + ), + ) + + ] diff --git a/ipatests/test_xmlrpc/test_hbactest_plugin.py b/ipatests/test_xmlrpc/test_hbactest_plugin.py new file mode 100644 index 000000000..520f20247 --- /dev/null +++ b/ipatests/test_xmlrpc/test_hbactest_plugin.py @@ -0,0 +1,217 @@ +# Authors: +# Pavel Zuna <pzuna@redhat.com> +# Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2009-2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/hbactest.py` module. +""" + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors +from types import NoneType +from nose.tools import raises + +# Test strategy: +# 1. Create few allow rules: with user categories, with explicit users, with user groups, with groups, with services +# 2. Create users for test +# 3. Run detailed and non-detailed tests for explicitly specified rules, check expected result +# +class test_hbactest(XMLRPC_test): + """ + Test the `hbactest` plugin. + """ + rule_names = [u'testing_rule1234_%d' % (d) for d in [1,2,3,4]] + rule_type = u'allow' + rule_service = u'ssh' + rule_descs = [u'description %d' % (d) for d in [1,2,3,4]] + + test_user = u'hbacrule_test_user' + test_group = u'hbacrule_test_group' + test_host = u'hbacrule.testhost' + test_hostgroup = u'hbacrule_test_hostgroup' + test_sourcehost = u'hbacrule.testsrchost' + test_sourcehostgroup = u'hbacrule_test_src_hostgroup' + test_service = u'ssh' + + # Auxiliary funcion for checking existence of warning for specified rule + def check_rule_presence(self,rule_name,warnings): + for warning in warnings: + if rule_name in warning: + return True + return False + + def test_0_hbactest_addrules(self): + """ + Prepare data by adding test HBAC rules using `xmlrpc.hbacrule_add'. + """ + + self.failsafe_add(api.Object.user, + self.test_user, givenname=u'first', sn=u'last' + ) + self.failsafe_add(api.Object.group, + self.test_group, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_host, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_hostgroup, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_sourcehost, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_sourcehostgroup, description=u'desc' + ) + self.failsafe_add(api.Object.hbacsvc, + self.test_service, description=u'desc' + ) + + for i in [0,1,2,3]: + api.Command['hbacrule_add']( + self.rule_names[i], accessruletype=self.rule_type, description=self.rule_descs[i], + ) + + ret = api.Command['hbacrule_add_user']( + self.rule_names[i], user=self.test_user, group=self.test_group + ) + + ret = api.Command['hbacrule_add_host']( + self.rule_names[i], host=self.test_host, hostgroup=self.test_hostgroup + ) + + ret = api.Command['hbacrule_add_service']( + self.rule_names[i], hbacsvc=self.test_service + ) + + if i & 1: + ret = api.Command['hbacrule_disable'](self.rule_names[i]) + + def test_a_hbactest_check_rules_detail(self): + """ + Test 'ipa hbactest --rules' (explicit IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + service=self.test_service, + rules=self.rule_names + ) + assert ret['value'] == True + assert type(ret['error']) == NoneType + for i in [0,1,2,3]: + assert self.rule_names[i] in ret['matched'] + + def test_b_hbactest_check_rules_nodetail(self): + """ + Test 'ipa hbactest --rules --nodetail' (explicit IPA rules, no detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + service=self.test_service, + rules=self.rule_names, + nodetail=True + ) + assert ret['value'] == True + assert ret['error'] == None + assert ret['matched'] == None + assert ret['notmatched'] == None + + def test_c_hbactest_check_rules_enabled_detail(self): + """ + Test 'ipa hbactest --enabled' (all enabled IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + service=self.test_service, + enabled=True + ) + # --enabled will try to work with _all_ enabled rules in IPA database + # It means we could have matched something else (unlikely but possible) + # Thus, check that our two enabled rules are in matched, nothing more + for i in [0,2]: + assert self.rule_names[i] in ret['matched'] + + def test_d_hbactest_check_rules_disabled_detail(self): + """ + Test 'ipa hbactest --disabled' (all disabled IPA rules, detailed output) + """ + ret = api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + service=self.test_service, + disabled=True + ) + # --disabled will try to work with _all_ disabled rules in IPA database + # It means we could have matched something else (unlikely but possible) + # Thus, check that our two disabled rules are in matched, nothing more + for i in [1,3]: + assert self.rule_names[i] in ret['matched'] + + def test_e_hbactest_check_non_existing_rule_detail(self): + """ + Test running 'ipa hbactest' with non-existing rule in --rules + """ + ret = api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + service=self.test_service, + rules=[u'%s_1x1' % (rule) for rule in self.rule_names], + nodetail=True + ) + + assert ret['value'] == False + assert ret['matched'] == None + assert ret['notmatched'] == None + for rule in self.rule_names: + assert u'%s_1x1' % (rule) in ret['error'] + + @raises(errors.ValidationError) + def test_f_hbactest_check_sourcehost_option_is_deprecated(self): + """ + Test running 'ipa hbactest' with --srchost option raises ValidationError + """ + api.Command['hbactest']( + user=self.test_user, + targethost=self.test_host, + sourcehost=self.test_sourcehost, + service=self.test_service, + rules=[u'%s_1x1' % rule for rule in self.rule_names], + nodetail=True + ) + + def test_g_hbactest_clear_testing_data(self): + """ + Clear data for HBAC test plugin testing. + """ + for i in [0,1,2,3]: + api.Command['hbacrule_remove_host'](self.rule_names[i], host=self.test_host) + api.Command['hbacrule_remove_host'](self.rule_names[i], hostgroup=self.test_hostgroup) + api.Command['hbacrule_del'](self.rule_names[i]) + + api.Command['user_del'](self.test_user) + api.Command['group_del'](self.test_group) + api.Command['host_del'](self.test_host) + api.Command['hostgroup_del'](self.test_hostgroup) + api.Command['host_del'](self.test_sourcehost) + api.Command['hostgroup_del'](self.test_sourcehostgroup) + api.Command['hbacsvc_del'](self.test_service) + diff --git a/ipatests/test_xmlrpc/test_host_plugin.py b/ipatests/test_xmlrpc/test_host_plugin.py new file mode 100644 index 000000000..a23a34112 --- /dev/null +++ b/ipatests/test_xmlrpc/test_host_plugin.py @@ -0,0 +1,939 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2008, 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib.plugins.host` module. +""" + +import os +import tempfile +from ipapython import ipautil +from ipalib import api, errors, x509 +from ipapython.dn import DN +from nose.tools import raises, assert_raises +from nose.plugins.skip import Skip, SkipTest +from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, XMLRPC_test, + fuzzy_uuid, fuzzy_digits, fuzzy_hash, fuzzy_date, fuzzy_issuer, + fuzzy_hex) +from ipatests.test_xmlrpc import objectclasses +import base64 + + +fqdn1 = u'testhost1.%s' % api.env.domain +short1 = u'testhost1' +dn1 = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'), + api.env.basedn) +service1 = u'dns/%s@%s' % (fqdn1, api.env.realm) +service1dn = DN(('krbprincipalname',service1.lower()),('cn','services'), + ('cn','accounts'),api.env.basedn) +fqdn2 = u'shouldnotexist.%s' % api.env.domain +dn2 = DN(('fqdn',fqdn2),('cn','computers'),('cn','accounts'), + api.env.basedn) +fqdn3 = u'testhost2.%s' % api.env.domain +short3 = u'testhost2' +dn3 = DN(('fqdn',fqdn3),('cn','computers'),('cn','accounts'), + api.env.basedn) +fqdn4 = u'testhost2.lab.%s' % api.env.domain +dn4 = DN(('fqdn',fqdn4),('cn','computers'),('cn','accounts'), + api.env.basedn) +invalidfqdn1 = u'foo_bar.lab.%s' % api.env.domain + +# We can use the same cert we generated for the service tests +fd = open('ipatests/test_xmlrpc/service.crt', 'r') +servercert = fd.readlines() +servercert = ''.join(servercert) +servercert = x509.strip_header(servercert) +fd.close() + +sshpubkey = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGAX3xAeLeaJggwTqMjxNwa6XHBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGIwA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNmcSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM019Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF0L public key test' +sshpubkeyfp = u'13:67:6B:BF:4E:A2:05:8E:AE:25:8B:A1:31:DE:6F:1B public key test (ssh-rsa)' + +class test_host(Declarative): + + cleanup_commands = [ + ('host_del', [fqdn1], {}), + ('host_del', [fqdn2], {}), + ('host_del', [fqdn3], {}), + ('host_del', [fqdn4], {}), + ('service_del', [service1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % fqdn1, + command=('host_show', [fqdn1], {}), + expected=errors.NotFound( + reason=u'%s: host not found' % fqdn1), + ), + + + dict( + desc='Try to update non-existent %r' % fqdn1, + command=('host_mod', [fqdn1], dict(description=u'Nope')), + expected=errors.NotFound( + reason=u'%s: host not found' % fqdn1), + ), + + + dict( + desc='Try to delete non-existent %r' % fqdn1, + command=('host_del', [fqdn1], {}), + expected=errors.NotFound( + reason=u'%s: host not found' % fqdn1), + ), + + + dict( + desc='Create %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=errors.DuplicateEntry(message=u'host with name ' + + u'"%s" already exists' % fqdn1), + ), + + + dict( + desc='Retrieve %r' % fqdn1, + command=('host_show', [fqdn1], {}), + expected=dict( + value=fqdn1, + summary=None, + result=dict( + dn=dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Retrieve %r with all=True' % fqdn1, + command=('host_show', [fqdn1], dict(all=True)), + expected=dict( + value=fqdn1, + summary=None, + result=dict( + dn=dn1, + cn=[fqdn1], + fqdn=[fqdn1], + description=[u'Test host 1'], + # FIXME: Why is 'localalityname' returned as 'l' with --all? + # It is intuitive for --all to return additional attributes, + # but not to return existing attributes under different + # names. + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + serverhostname=[u'testhost1'], + objectclass=objectclasses.host, + managedby_host=[fqdn1], + managing_host=[fqdn1], + ipauniqueid=[fuzzy_uuid], + has_keytab=False, + has_password=False, + ipakrbokasdelegate=False, + ipakrbrequirespreauth=True, + ), + ), + ), + + + dict( + desc='Search for %r' % fqdn1, + command=('host_find', [fqdn1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 host matched', + result=[ + dict( + dn=dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + managedby_host=[u'%s' % fqdn1], + has_keytab=False, + has_password=False, + ), + ], + ), + ), + + + dict( + desc='Search for %r with all=True' % fqdn1, + command=('host_find', [fqdn1], dict(all=True)), + expected=dict( + count=1, + truncated=False, + summary=u'1 host matched', + result=[ + dict( + dn=dn1, + cn=[fqdn1], + fqdn=[fqdn1], + description=[u'Test host 1'], + # FIXME: Why is 'localalityname' returned as 'l' with --all? + # It is intuitive for --all to return additional attributes, + # but not to return existing attributes under different + # names. + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + serverhostname=[u'testhost1'], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn1], + managing_host=[u'%s' % fqdn1], + has_keytab=False, + has_password=False, + ipakrbokasdelegate=False, + ipakrbrequirespreauth=True, + ), + ], + ), + ), + + + dict( + desc='Update %r' % fqdn1, + command=('host_mod', [fqdn1], dict(description=u'Updated host 1', + usercertificate=servercert)), + expected=dict( + value=fqdn1, + summary=u'Modified host "%s"' % fqdn1, + result=dict( + description=[u'Updated host 1'], + fqdn=[fqdn1], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + managedby_host=[u'%s' % fqdn1], + usercertificate=[base64.b64decode(servercert)], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % fqdn1, + command=('host_show', [fqdn1], {}), + expected=dict( + value=fqdn1, + summary=None, + result=dict( + dn=dn1, + fqdn=[fqdn1], + description=[u'Updated host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[u'%s' % fqdn1], + usercertificate=[base64.b64decode(servercert)], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + ), + ), + ), + + dict( + desc='Create %r' % fqdn3, + command=('host_add', [fqdn3], + dict( + description=u'Test host 2', + l=u'Undisclosed location 2', + force=True, + ), + ), + expected=dict( + value=fqdn3, + summary=u'Added host "%s"' % fqdn3, + result=dict( + dn=dn3, + fqdn=[fqdn3], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn3], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % fqdn4, + command=('host_add', [fqdn4], + dict( + description=u'Test host 4', + l=u'Undisclosed location 4', + force=True, + ), + ), + expected=dict( + value=fqdn4, + summary=u'Added host "%s"' % fqdn4, + result=dict( + dn=dn4, + fqdn=[fqdn4], + description=[u'Test host 4'], + l=[u'Undisclosed location 4'], + krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn4], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Add managedby_host %r to %r' % (fqdn1, fqdn3), + command=('host_add_managedby', [fqdn3], + dict( + host=u'%s' % fqdn1, + ), + ), + expected=dict( + completed=1, + failed=dict( + managedby = dict( + host=tuple(), + ), + ), + result=dict( + dn=dn3, + fqdn=[fqdn3], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + managedby_host=[u'%s' % fqdn3, u'%s' % fqdn1], + ), + ), + ), + + dict( + desc='Retrieve %r' % fqdn3, + command=('host_show', [fqdn3], {}), + expected=dict( + value=fqdn3, + summary=None, + result=dict( + dn=dn3, + fqdn=[fqdn3], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[u'%s' % fqdn3, u'%s' % fqdn1], + ), + ), + ), + + dict( + desc='Search for hosts with --man-hosts and --not-man-hosts', + command=('host_find', [], {'man_host' : fqdn3, 'not_man_host' : fqdn1}), + expected=dict( + count=1, + truncated=False, + summary=u'1 host matched', + result=[ + dict( + dn=dn3, + fqdn=[fqdn3], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[u'%s' % fqdn3, u'%s' % fqdn1], + ), + ], + ), + ), + + dict( + desc='Try to search for hosts with --man-hosts', + command=('host_find', [], {'man_host' : [fqdn3,fqdn4]}), + expected=dict( + count=0, + truncated=False, + summary=u'0 hosts matched', + result=[], + ), + ), + + dict( + desc='Remove managedby_host %r from %r' % (fqdn1, fqdn3), + command=('host_remove_managedby', [fqdn3], + dict( + host=u'%s' % fqdn1, + ), + ), + expected=dict( + completed=1, + failed=dict( + managedby = dict( + host=tuple(), + ), + ), + result=dict( + dn=dn3, + fqdn=[fqdn3], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn3, api.env.realm)], + managedby_host=[u'%s' % fqdn3], + ), + ), + ), + + + dict( + desc='Show a host with multiple matches %s' % short3, + command=('host_show', [short3], {}), + expected=errors.SingleMatchExpected(found=2), + ), + + + dict( + desc='Try to rename %r' % fqdn1, + command=('host_mod', [fqdn1], dict(setattr=u'fqdn=changed.example.com')), + expected=errors.NotAllowedOnRDN() + ), + + + dict( + desc='Add MAC address to %r' % fqdn1, + command=('host_mod', [fqdn1], dict(macaddress=u'00:50:56:30:F6:5F')), + expected=dict( + value=fqdn1, + summary=u'Modified host "%s"' % fqdn1, + result=dict( + description=[u'Updated host 1'], + fqdn=[fqdn1], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + managedby_host=[u'%s' % fqdn1], + usercertificate=[base64.b64decode(servercert)], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + macaddress=[u'00:50:56:30:F6:5F'], + issuer=fuzzy_issuer, + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Add another MAC address to %r' % fqdn1, + command=('host_mod', [fqdn1], dict(macaddress=[u'00:50:56:30:F6:5F', u'00:50:56:2C:8D:82'])), + expected=dict( + value=fqdn1, + summary=u'Modified host "%s"' % fqdn1, + result=dict( + description=[u'Updated host 1'], + fqdn=[fqdn1], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + managedby_host=[u'%s' % fqdn1], + usercertificate=[base64.b64decode(servercert)], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + macaddress=[u'00:50:56:30:F6:5F', u'00:50:56:2C:8D:82'], + issuer=fuzzy_issuer, + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Add an illegal MAC address to %r' % fqdn1, + command=('host_mod', [fqdn1], dict(macaddress=[u'xx'])), + expected=errors.ValidationError(name='macaddress', + error=u'Must be of the form HH:HH:HH:HH:HH:HH, where ' + + u'each H is a hexadecimal character.'), + ), + + + dict( + desc='Add SSH public key to %r' % fqdn1, + command=('host_mod', [fqdn1], dict(ipasshpubkey=[sshpubkey])), + expected=dict( + value=fqdn1, + summary=u'Modified host "%s"' % fqdn1, + result=dict( + description=[u'Updated host 1'], + fqdn=[fqdn1], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + managedby_host=[u'%s' % fqdn1], + usercertificate=[base64.b64decode(servercert)], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + macaddress=[u'00:50:56:30:F6:5F', u'00:50:56:2C:8D:82'], + ipasshpubkey=[sshpubkey], + sshpubkeyfp=[sshpubkeyfp], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Add an illegal SSH public key to %r' % fqdn1, + command=('host_mod', [fqdn1], dict(ipasshpubkey=[u'no-pty %s' % sshpubkey])), + expected=errors.ValidationError(name='sshpubkey', + error=u'options are not allowed'), + ), + + + dict( + desc='Delete %r' % fqdn1, + command=('host_del', [fqdn1], {}), + expected=dict( + value=fqdn1, + summary=u'Deleted host "%s"' % fqdn1, + result=dict(failed=u''), + ), + ), + + + dict( + desc='Try to retrieve non-existent %r' % fqdn1, + command=('host_show', [fqdn1], {}), + expected=errors.NotFound(reason=u'%s: host not found' % fqdn1), + ), + + + dict( + desc='Try to update non-existent %r' % fqdn1, + command=('host_mod', [fqdn1], dict(description=u'Nope')), + expected=errors.NotFound(reason=u'%s: host not found' % fqdn1), + ), + + + dict( + desc='Try to delete non-existent %r' % fqdn1, + command=('host_del', [fqdn1], {}), + expected=errors.NotFound(reason=u'%s: host not found' % fqdn1), + ), + + # Test deletion using a non-fully-qualified hostname. Services + # associated with this host should also be removed. + dict( + desc='Re-create %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + dict( + desc='Add a service to host %r' % fqdn1, + command=('service_add', [service1], {'force': True}), + expected=dict( + value=service1, + summary=u'Added service "%s"' % service1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + objectclass=objectclasses.service, + managedby_host=[fqdn1], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + dict( + desc='Delete using host name %r' % short1, + command=('host_del', [short1], {}), + expected=dict( + value=short1, + summary=u'Deleted host "%s"' % short1, + result=dict(failed=u''), + ), + ), + + dict( + desc='Search for services for %r' % fqdn1, + command=('service_find', [fqdn1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 services matched', + result=[ + ], + ), + ), + + + dict( + desc='Try to add host not in DNS %r without force' % fqdn2, + command=('host_add', [fqdn2], {}), + expected=errors.DNSNotARecordError( + reason=u'Host does not have corresponding DNS A record'), + ), + + + dict( + desc='Try to add host not in DNS %r with force' % fqdn2, + command=('host_add', [fqdn2], + dict( + description=u'Test host 2', + l=u'Undisclosed location 2', + userclass=[u'webserver', u'mailserver'], + force=True, + ), + ), + expected=dict( + value=fqdn2, + summary=u'Added host "%s"' % fqdn2, + result=dict( + dn=dn2, + fqdn=[fqdn2], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn2], + userclass=[u'webserver', u'mailserver'], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Retrieve %r' % fqdn2, + command=('host_show', [fqdn2], {}), + expected=dict( + value=fqdn2, + summary=None, + result=dict( + dn=dn2, + fqdn=[fqdn2], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[fqdn2], + userclass=[u'webserver', u'mailserver'], + ), + ), + ), + + + # This test will only succeed when running against lite-server.py + # on same box as IPA install. + dict( + desc='Delete the current host (master?) %s should be caught' % api.env.host, + command=('host_del', [api.env.host], {}), + expected=errors.ValidationError(name='hostname', + error=u'An IPA master host cannot be deleted or disabled'), + ), + + + dict( + desc='Disable the current host (master?) %s should be caught' % api.env.host, + command=('host_disable', [api.env.host], {}), + expected=errors.ValidationError(name='hostname', + error=u'An IPA master host cannot be deleted or disabled'), + ), + + + dict( + desc='Test that validation is enabled on adds', + command=('host_add', [invalidfqdn1], {}), + expected=errors.ValidationError(name='hostname', + error=u'invalid domain-name: only letters, numbers, and - ' + + u'are allowed. DNS label may not start or end with -'), + ), + + + # The assumption on these next 4 tests is that if we don't get a + # validation error then the request was processed normally. + dict( + desc='Test that validation is disabled on mods', + command=('host_mod', [invalidfqdn1], {}), + expected=errors.NotFound( + reason=u'%s: host not found' % invalidfqdn1), + ), + + + dict( + desc='Test that validation is disabled on deletes', + command=('host_del', [invalidfqdn1], {}), + expected=errors.NotFound( + reason=u'%s: host not found' % invalidfqdn1), + ), + + + dict( + desc='Test that validation is disabled on show', + command=('host_show', [invalidfqdn1], {}), + expected=errors.NotFound( + reason=u'%s: host not found' % invalidfqdn1), + ), + + + dict( + desc='Test that validation is disabled on find', + command=('host_find', [invalidfqdn1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 hosts matched', + result=[], + ), + ), + + + dict( + desc='Add managedby_host %r to %r' % (fqdn3, fqdn4), + command=('host_add_managedby', [fqdn4], dict(host=fqdn3,), + ), + expected=dict( + completed=1, + failed=dict( + managedby = dict( + host=tuple(), + ), + ), + result=dict( + dn=dn4, + fqdn=[fqdn4], + description=[u'Test host 4'], + l=[u'Undisclosed location 4'], + krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)], + managedby_host=[fqdn4, fqdn3], + ), + ), + ), + + + dict( + desc='Delete %r' % fqdn3, + command=('host_del', [fqdn3], {}), + expected=dict( + value=fqdn3, + summary=u'Deleted host "%s"' % fqdn3, + result=dict(failed=u''), + ), + ), + + + dict( + desc='Retrieve %r to verify that %r is gone from managedBy' % (fqdn4, fqdn3), + command=('host_show', [fqdn4], {}), + expected=dict( + value=fqdn4, + summary=None, + result=dict( + dn=dn4, + fqdn=[fqdn4], + description=[u'Test host 4'], + l=[u'Undisclosed location 4'], + krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[fqdn4], + ), + ), + ), + + ] + +class test_host_false_pwd_change(XMLRPC_test): + + fqdn1 = u'testhost1.%s' % api.env.domain + short1 = u'testhost1' + new_pass = u'pass_123' + command = "ipa-client/ipa-join" + + @classmethod + def setUpClass(cls): + [cls.keytabfd,cls.keytabname] = tempfile.mkstemp() + os.close(cls.keytabfd) + + does_command_exist = os.path.isfile(cls.command) + + if not does_command_exist: + raise SkipTest("Command '%s' not found" % cls.command) + + # auxiliary function for checking whether the join operation has set + # correct attributes + def host_joined(self): + ret = api.Command['host_show'](self.fqdn1, all=True) + assert (ret['result']['has_keytab'] == True) + assert (ret['result']['has_password'] == False) + + def test_a_join_host(self): + """ + Create a test host and join him into IPA. + """ + + # create a test host with bulk enrollment password + random_pass = api.Command['host_add'](self.fqdn1, random=True, force=True)['result']['randompassword'] + + # joint the host with the bulk password + new_args = [self.command, + "-s", api.env.host, + "-h", self.fqdn1, + "-k", self.keytabname, + "-w", random_pass, + "-q", + ] + try: + # join operation may fail on 'adding key into keytab', but + # the keytab is not necessary for further tests + (out, err, rc) = ipautil.run(new_args, None) + except ipautil.CalledProcessError, e: + pass + finally: + self.host_joined() + + @raises(errors.ValidationError) + def test_b_try_password(self): + """ + Try to change the password of enrolled host with specified password + """ + api.Command['host_mod'](self.fqdn1, userpassword=self.new_pass) + + @raises(errors.ValidationError) + def test_c_try_random(self): + """ + Try to change the password of enrolled host with random password + """ + api.Command['host_mod'](self.fqdn1, random=True) + + def test_d_cleanup(self): + """ + Clean up test data + """ + os.unlink(self.keytabname) + api.Command['host_del'](self.fqdn1) + # verify that it's gone + with assert_raises(errors.NotFound): + api.Command['host_show'](self.fqdn1) diff --git a/ipatests/test_xmlrpc/test_hostgroup_plugin.py b/ipatests/test_xmlrpc/test_hostgroup_plugin.py new file mode 100644 index 000000000..b610979ec --- /dev/null +++ b/ipatests/test_xmlrpc/test_hostgroup_plugin.py @@ -0,0 +1,313 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2008, 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib.plugins.hostgroup` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid +from ipatests.test_xmlrpc import objectclasses +from ipapython.dn import DN + +hostgroup1 = u'testhostgroup1' +dn1 = DN(('cn',hostgroup1),('cn','hostgroups'),('cn','accounts'), + api.env.basedn) + +hostgroup_single = u'a' +dn_single = DN(('cn',hostgroup_single),('cn','hostgroups'),('cn','accounts'), + api.env.basedn) + +fqdn1 = u'testhost1.%s' % api.env.domain +host_dn1 = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'), + api.env.basedn) + +invalidhostgroup1 = u'@invalid' + + +class test_hostgroup(Declarative): + + cleanup_commands = [ + ('hostgroup_del', [hostgroup1], {}), + ('host_del', [fqdn1], {}), + ] + + tests=[ + + dict( + desc='Try to retrieve non-existent %r' % hostgroup1, + command=('hostgroup_show', [hostgroup1], {}), + expected=errors.NotFound( + reason=u'%s: host group not found' % hostgroup1), + ), + + + dict( + desc='Try to update non-existent %r' % hostgroup1, + command=('hostgroup_mod', [hostgroup1], + dict(description=u'Updated hostgroup 1') + ), + expected=errors.NotFound( + reason=u'%s: host group not found' % hostgroup1), + ), + + + dict( + desc='Try to delete non-existent %r' % hostgroup1, + command=('hostgroup_del', [hostgroup1], {}), + expected=errors.NotFound( + reason=u'%s: host group not found' % hostgroup1), + ), + + + dict( + desc='Test an invalid hostgroup name %r' % invalidhostgroup1, + command=('hostgroup_add', [invalidhostgroup1], dict(description=u'Test')), + expected=errors.ValidationError(name='hostgroup_name', + error=u'may only include letters, numbers, _, -, and .'), + ), + + + dict( + desc='Create %r' % hostgroup1, + command=('hostgroup_add', [hostgroup1], + dict(description=u'Test hostgroup 1') + ), + expected=dict( + value=hostgroup1, + summary=u'Added hostgroup "testhostgroup1"', + result=dict( + dn=dn1, + cn=[hostgroup1], + objectclass=objectclasses.hostgroup, + description=[u'Test hostgroup 1'], + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % hostgroup1, + command=('hostgroup_add', [hostgroup1], + dict(description=u'Test hostgroup 1') + ), + expected=errors.DuplicateEntry(message= + u'host group with name "%s" already exists' % hostgroup1), + ), + + + dict( + desc='Create host %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=host_dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc=u'Add host %r to %r' % (fqdn1, hostgroup1), + command=( + 'hostgroup_add_member', [hostgroup1], dict(host=fqdn1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + host=tuple(), + hostgroup=tuple(), + ), + ), + result={ + 'dn': dn1, + 'cn': [hostgroup1], + 'description': [u'Test hostgroup 1'], + 'member_host': [fqdn1], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup1, + command=('hostgroup_show', [hostgroup1], {}), + expected=dict( + value=hostgroup1, + summary=None, + result={ + 'dn': dn1, + 'member_host': [u'testhost1.%s' % api.env.domain], + 'cn': [hostgroup1], + 'description': [u'Test hostgroup 1'], + }, + ), + ), + + + dict( + desc='Search for %r' % hostgroup1, + command=('hostgroup_find', [], dict(cn=hostgroup1)), + expected=dict( + count=1, + truncated=False, + summary=u'1 hostgroup matched', + result=[ + { + 'dn': dn1, + 'member_host': [u'testhost1.%s' % api.env.domain], + 'cn': [hostgroup1], + 'description': [u'Test hostgroup 1'], + }, + ], + ), + ), + + + dict( + desc='Update %r' % hostgroup1, + command=('hostgroup_mod', [hostgroup1], + dict(description=u'Updated hostgroup 1') + ), + expected=dict( + value=hostgroup1, + summary=u'Modified hostgroup "testhostgroup1"', + result=dict( + cn=[hostgroup1], + description=[u'Updated hostgroup 1'], + member_host=[u'testhost1.%s' % api.env.domain], + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % hostgroup1, + command=('hostgroup_show', [hostgroup1], {}), + expected=dict( + value=hostgroup1, + summary=None, + result={ + 'dn': dn1, + 'member_host': [u'testhost1.%s' % api.env.domain], + 'cn': [hostgroup1], + 'description': [u'Updated hostgroup 1'], + }, + ), + ), + + + dict( + desc='Remove host %r from %r' % (fqdn1, hostgroup1), + command=('hostgroup_remove_member', [hostgroup1], + dict(host=fqdn1) + ), + expected=dict( + failed=dict( + member=dict( + host=tuple(), + hostgroup=tuple(), + ), + ), + completed=1, + result={ + 'dn': dn1, + 'cn': [hostgroup1], + 'description': [u'Updated hostgroup 1'], + }, + ), + ), + + + dict( + desc='Delete %r' % hostgroup1, + command=('hostgroup_del', [hostgroup1], {}), + expected=dict( + value=hostgroup1, + summary=u'Deleted hostgroup "testhostgroup1"', + result=dict(failed=u''), + ), + ), + + + dict( + desc='Create hostgroup with name containing only one letter: %r' % hostgroup_single, + command=('hostgroup_add', [hostgroup_single], + dict(description=u'Test hostgroup with single letter in name') + ), + expected=dict( + value=hostgroup_single, + summary=u'Added hostgroup "a"', + result=dict( + dn=dn_single, + cn=[hostgroup_single], + objectclass=objectclasses.hostgroup, + description=[u'Test hostgroup with single letter in name'], + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn',hostgroup_single),('cn','ng'),('cn','alt'), + api.env.basedn)], + ), + ), + ), + + + dict( + desc='Delete %r' % hostgroup_single, + command=('hostgroup_del', [hostgroup_single], {}), + expected=dict( + value=hostgroup_single, + summary=u'Deleted hostgroup "a"', + result=dict(failed=u''), + ), + ), + + + dict( + desc='Delete host %r' % fqdn1, + command=('host_del', [fqdn1], {}), + expected=dict( + value=fqdn1, + summary=u'Deleted host "%s"' % fqdn1, + result=dict(failed=u''), + ), + ) + + ] diff --git a/ipatests/test_xmlrpc/test_krbtpolicy.py b/ipatests/test_xmlrpc/test_krbtpolicy.py new file mode 100644 index 000000000..b940c5e5d --- /dev/null +++ b/ipatests/test_xmlrpc/test_krbtpolicy.py @@ -0,0 +1,150 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test kerberos ticket policy +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +user1 = u'tuser1' + +class test_krbtpolicy(Declarative): + cleanup_commands = [ + ('user_del', [user1], {}), + ('krbtpolicy_reset', [], {}), + ] + + tests = [ + + + dict( + desc='Reset global policy', + command=( + 'krbtpolicy_reset', [], {} + ), + expected=dict( + value=u'', + summary=None, + result=dict( + krbmaxticketlife=[u'86400'], + krbmaxrenewableage=[u'604800'], + ), + ), + ), + + + dict( + desc='Show global policy', + command=( + 'krbtpolicy_show', [], {} + ), + expected=dict( + value=u'', + summary=None, + result=dict( + dn=DN(('cn',api.env.domain),('cn','kerberos'), + api.env.basedn), + krbmaxticketlife=[u'86400'], + krbmaxrenewableage=[u'604800'], + ), + ), + ), + + + dict( + desc='Update global policy', + command=( + 'krbtpolicy_mod', [], dict(krbmaxticketlife=3600) + ), + expected=dict( + value=u'', + summary=None, + result=dict( + krbmaxticketlife=[u'3600'], + krbmaxrenewableage=[u'604800'], + ), + ), + ), + + + dict( + desc='Create %r' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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='Update user ticket policy', + command=( + 'krbtpolicy_mod', [user1], dict(krbmaxticketlife=3600) + ), + expected=dict( + value=user1, + summary=None, + result=dict( + krbmaxticketlife=[u'3600'], + ), + ), + ), + + + dict( + desc='Try updating other user attribute', + command=( + 'krbtpolicy_mod', [user1], dict(setattr=u'givenname=Pete') + ), + expected=errors.ObjectclassViolation(info='attribute "givenname" not allowed'), + ), + + + ] diff --git a/ipatests/test_xmlrpc/test_nesting.py b/ipatests/test_xmlrpc/test_nesting.py new file mode 100644 index 000000000..5c093c93a --- /dev/null +++ b/ipatests/test_xmlrpc/test_nesting.py @@ -0,0 +1,797 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test group nexting an indirect members +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +group1 = u'testgroup1' +group2 = u'testgroup2' +group3 = u'testgroup3' +group4 = u'testgroup4' +user1 = u'tuser1' +user2 = u'tuser2' +user3 = u'tuser3' +user4 = u'tuser4' + +hostgroup1 = u'testhostgroup1' +hgdn1 = DN(('cn',hostgroup1),('cn','hostgroups'),('cn','accounts'), + api.env.basedn) +hostgroup2 = u'testhostgroup2' +hgdn2 = DN(('cn',hostgroup2),('cn','hostgroups'),('cn','accounts'), + api.env.basedn) + +fqdn1 = u'testhost1.%s' % api.env.domain +host_dn1 = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'), + api.env.basedn) + + +class test_nesting(Declarative): + cleanup_commands = [ + ('group_del', [group1], {}), + ('group_del', [group2], {}), + ('group_del', [group3], {}), + ('group_del', [group4], {}), + ('user_del', [user1], {}), + ('user_del', [user2], {}), + ('user_del', [user3], {}), + ('user_del', [user4], {}), + ('host_del', [fqdn1], {}), + ('hostgroup_del', [hostgroup1], {}), + ('hostgroup_del', [hostgroup2], {}), + ] + + tests = [ + + ################ + # create group1: + + dict( + desc='Create %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc 1') + ), + expected=dict( + value=group1, + summary=u'Added group "testgroup1"', + result=dict( + cn=[group1], + description=[u'Test desc 1'], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + gidnumber=[fuzzy_digits], + dn=DN(('cn','testgroup1'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + ################ + # create group2: + dict( + desc='Create %r' % group2, + command=( + 'group_add', [group2], dict(description=u'Test desc 2') + ), + expected=dict( + value=group2, + summary=u'Added group "testgroup2"', + result=dict( + cn=[group2], + description=[u'Test desc 2'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn','testgroup2'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % group3, + command=( + 'group_add', [group3], dict(description=u'Test desc 3') + ), + expected=dict( + value=group3, + summary=u'Added group "testgroup3"', + result=dict( + cn=[group3], + description=[u'Test desc 3'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn','testgroup3'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % group4, + command=( + 'group_add', [group4], dict(description=u'Test desc 4') + ), + expected=dict( + value=group4, + summary=u'Added group "testgroup4"', + result=dict( + cn=[group4], + description=[u'Test desc 4'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn','testgroup4'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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 %r' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2') + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user2, api.env.domain)], + displayname=[u'Test User2'], + cn=[u'Test User2'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=DN(('uid',user2),('cn','users'),('cn','accounts'), + api.env.basedn) + ), + ), + ), + + + dict( + desc='Create %r' % user3, + command=( + 'user_add', [user3], dict(givenname=u'Test', sn=u'User3') + ), + expected=dict( + value=user3, + summary=u'Added user "%s"' % user3, + result=dict( + gecos=[u'Test User3'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser3'], + krbprincipalname=[u'tuser3@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User3'], + uid=[user3], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user3, api.env.domain)], + displayname=[u'Test User3'], + cn=[u'Test User3'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user3),('cn','groups'),('cn','accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=DN(('uid',user3),('cn','users'),('cn','accounts'), + api.env.basedn) + ), + ), + ), + + + dict( + desc='Create %r' % user4, + command=( + 'user_add', [user4], dict(givenname=u'Test', sn=u'User4') + ), + expected=dict( + value=user4, + summary=u'Added user "%s"' % user4, + result=dict( + gecos=[u'Test User4'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser4'], + krbprincipalname=[u'tuser4@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User4'], + uid=[user4], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user4, api.env.domain)], + displayname=[u'Test User4'], + cn=[u'Test User4'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user4),('cn','groups'),('cn','accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=DN(('uid',user4),('cn','users'),('cn','accounts'), + api.env.basedn) + ), + ), + ), + + + ############### + # member stuff + # + # Create 4 groups and 4 users and set the following membership: + # + # g1: + # no direct memberships + # + # g2: + # memberof: g1 + # member: user1, user2 + # + # g3: + # memberof: g1 + # member: user3, g4 + # + # g4: + # memberof: g3 + # member: user1, user4 + # + # So when we do a show it looks like: + # + # g1: + # member groups: g2, g3 + # indirect member group: g4 + # indirect member users: user1, user2, tuser3, tuser4 + # + # g2: + # member of group: g1 + # member users: tuser1, tuser2 + # + # g3: + # member users: tuser3 + # member groups: g4 + # member of groups: g1 + # indirect member users: tuser4 + # + # g4: + # member users: tuser1, tuser4 + # member of groups: g3 + # indirect member of groups: g1 + # + # Note that tuser1 is an indirect member of g1 both through + # g2 and g4. It should appear just once in the list. + + dict( + desc='Add a group member %r to %r' % (group2, group1), + command=( + 'group_add_member', [group1], dict(group=group2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_group': (group2,), + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'Test desc 1'], + }, + ), + ), + + + dict( + desc='Add a group member %r to %r' % (group3, group1), + command=( + 'group_add_member', [group1], dict(group=group3) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_group': [group2, group3,], + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'Test desc 1'], + }, + ), + ), + + + dict( + desc='Add a user member %r to %r' % (user1, group2), + command=( + 'group_add_member', [group2], dict(user=user1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group2),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': (u'tuser1',), + 'memberof_group': (u'testgroup1',), + 'gidnumber': [fuzzy_digits], + 'cn': [group2], + 'description': [u'Test desc 2'], + }, + ), + ), + + + dict( + desc='Add a user member %r to %r' % (user2, group2), + command=( + 'group_add_member', [group2], dict(user=user2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group2),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': [user1, user2], + 'memberof_group': [group1], + 'gidnumber': [fuzzy_digits], + 'cn': [group2], + 'description': [u'Test desc 2'], + }, + ), + ), + + + dict( + desc='Add a user member %r to %r' % (user3, group3), + command=( + 'group_add_member', [group3], dict(user=user3) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group3),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': [user3], + 'memberof_group': [group1], + 'gidnumber': [fuzzy_digits], + 'cn': [group3], + 'description': [u'Test desc 3'], + }, + ), + ), + + + dict( + desc='Add a group member %r to %r' % (group4, group3), + command=( + 'group_add_member', [group3], dict(group=group4) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group3),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': [user3], + 'memberof_group': [group1], + 'member_group': [group4], + 'gidnumber': [fuzzy_digits], + 'cn': [group3], + 'description': [u'Test desc 3'], + }, + ), + ), + + + dict( + desc='Add a user member %r to %r' % (user1, group4), + command=( + 'group_add_member', [group4], dict(user=user1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group4),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': [user1], + 'memberof_group': [group3], + 'memberofindirect_group': [group1], + 'gidnumber': [fuzzy_digits], + 'cn': [group4], + 'description': [u'Test desc 4'], + }, + ), + ), + + + dict( + desc='Add a user member %r to %r' % (user4, group4), + command=( + 'group_add_member', [group4], dict(user=user4) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group4),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': [user1, user4], + 'memberof_group': [group3], + 'memberofindirect_group': [group1], + 'gidnumber': [fuzzy_digits], + 'cn': [group4], + 'description': [u'Test desc 4'], + }, + ), + ), + + + dict( + desc='Retrieve group %r' % group1, + command=('group_show', [group1], {}), + expected=dict( + value=group1, + summary=None, + result=dict( + cn=[group1], + description=[u'Test desc 1'], + gidnumber= [fuzzy_digits], + memberindirect_group = [group4], + member_group = [group2, group3], + memberindirect_user = [user1, user2, user3, user4], + dn=DN(('cn','testgroup1'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Retrieve group %r' % group2, + command=('group_show', [group2], {}), + expected=dict( + value=group2, + summary=None, + result=dict( + cn=[group2], + description=[u'Test desc 2'], + gidnumber= [fuzzy_digits], + memberof_group = [group1], + member_user = [user1, user2], + dn=DN(('cn','testgroup2'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Retrieve group %r' % group3, + command=('group_show', [group3], {}), + expected=dict( + value=group3, + summary=None, + result=dict( + cn=[group3], + description=[u'Test desc 3'], + gidnumber= [fuzzy_digits], + memberof_group = [group1], + member_user = [user3], + member_group = [group4], + memberindirect_user = [user1, user4], + dn=DN(('cn','testgroup3'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Retrieve group %r' % group4, + command=('group_show', [group4], {}), + expected=dict( + value=group4, + summary=None, + result=dict( + cn=[group4], + description=[u'Test desc 4'], + gidnumber= [fuzzy_digits], + memberof_group = [group3], + member_user = [user1, user4], + memberofindirect_group = [group1], + dn=DN(('cn','testgroup4'),('cn','groups'), + ('cn','accounts'),api.env.basedn), + ), + ), + ), + + + # Now do something similar with hosts and hostgroups + dict( + desc='Create host %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=host_dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup1, + command=('hostgroup_add', [hostgroup1], + dict(description=u'Test hostgroup 1') + ), + expected=dict( + value=hostgroup1, + summary=u'Added hostgroup "testhostgroup1"', + result=dict( + dn=hgdn1, + cn=[hostgroup1], + objectclass=objectclasses.hostgroup, + description=[u'Test hostgroup 1'], + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup2, + command=('hostgroup_add', [hostgroup2], + dict(description=u'Test hostgroup 2') + ), + expected=dict( + value=hostgroup2, + summary=u'Added hostgroup "testhostgroup2"', + result=dict( + dn=hgdn2, + cn=[hostgroup2], + objectclass=objectclasses.hostgroup, + description=[u'Test hostgroup 2'], + ipauniqueid=[fuzzy_uuid], + mepmanagedentry=[DN(('cn',hostgroup2),('cn','ng'),('cn','alt'), + api.env.basedn)], + ), + ), + ), + + + dict( + desc=u'Add host %r to %r' % (fqdn1, hostgroup2), + command=( + 'hostgroup_add_member', [hostgroup2], dict(host=fqdn1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + host=tuple(), + hostgroup=tuple(), + ), + ), + result={ + 'dn': hgdn2, + 'cn': [hostgroup2], + 'description': [u'Test hostgroup 2'], + 'member_host': [fqdn1], + }, + ), + ), + + + dict( + desc=u'Add hostgroup %r to %r' % (hostgroup2, hostgroup1), + command=( + 'hostgroup_add_member', [hostgroup1], dict(hostgroup=hostgroup2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + host=tuple(), + hostgroup=tuple(), + ), + ), + result={ + 'dn': hgdn1, + 'cn': [hostgroup1], + 'description': [u'Test hostgroup 1'], + 'member_hostgroup': [hostgroup2], + 'memberindirect_host': [fqdn1], + }, + ), + ), + + + dict( + desc='Retrieve %r' % hostgroup1, + command=('hostgroup_show', [hostgroup1], {}), + expected=dict( + value=hostgroup1, + summary=None, + result={ + 'dn': hgdn1, + 'memberindirect_host': [u'testhost1.%s' % api.env.domain], + 'member_hostgroup': [hostgroup2], + 'cn': [hostgroup1], + 'description': [u'Test hostgroup 1'], + }, + ), + ), + + + dict( + desc='Retrieve %r' % fqdn1, + command=('host_show', [fqdn1], {}), + expected=dict( + value=fqdn1, + summary=None, + result=dict( + dn=host_dn1, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False, + has_password=False, + managedby_host=[fqdn1], + memberof_hostgroup = [u'testhostgroup2'], + memberofindirect_hostgroup = [u'testhostgroup1'], + ), + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_netgroup_plugin.py b/ipatests/test_xmlrpc/test_netgroup_plugin.py new file mode 100644 index 000000000..3dccac1bd --- /dev/null +++ b/ipatests/test_xmlrpc/test_netgroup_plugin.py @@ -0,0 +1,1362 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/netgroup.py` module. +""" + +import nose +import krbV +from ipalib import api +from ipalib import errors +from ipaserver.plugins.ldap2 import ldap2 +from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, fuzzy_digits, + fuzzy_uuid, fuzzy_netgroupdn) +from ipatests.test_xmlrpc import objectclasses +from ipapython.dn import DN + +# Global so we can save the value between tests +netgroup_dn = None + +# See if our LDAP server is up and we can talk to it over GSSAPI +ccache = krbV.default_context().default_ccache().name + +netgroup1 = u'netgroup1' +netgroup2 = u'netgroup2' +netgroup_single = u'a' + +host1 = u'ipatesthost.%s' % api.env.domain +host_dn1 = DN(('fqdn',host1),('cn','computers'),('cn','accounts'), + api.env.basedn) + +unknown_host = u'unknown' + +unknown_host2 = u'unknown2' + +hostgroup1 = u'hg1' +hostgroup_dn1 = DN(('cn',hostgroup1),('cn','hostgroups'),('cn','accounts'), + api.env.basedn) + +user1 = u'jexample' + +# user2 is a member of testgroup +user2 = u'pexample' + +group1 = u'testgroup' + +invalidnetgroup1=u'+badnetgroup' +invalidnisdomain1=u'domain1,domain2' +invalidnisdomain2=u'+invalidnisdomain' +invalidhost=u'+invalid&host' + +class test_netgroup(Declarative): + """ + Test the `netgroup` plugin. + """ + + cleanup_commands = [ + ('netgroup_del', [netgroup1], {}), + ('netgroup_del', [netgroup2], {}), + ('host_del', [host1], {}), + ('hostgroup_del', [hostgroup1], {}), + ('user_del', [user1], {}), + ('user_del', [user2], {}), + ('group_del', [group1], {}), + ] + + tests=[ + + dict( + desc='Try to retrieve non-existent %r' % netgroup1, + command=('netgroup_show', [netgroup1], {}), + expected=errors.NotFound( + reason=u'%s: netgroup not found' % netgroup1), + ), + + + dict( + desc='Try to update non-existent %r' % netgroup1, + command=('netgroup_mod', [netgroup1], + dict(description=u'Updated hostgroup 1') + ), + expected=errors.NotFound( + reason=u'%s: netgroup not found' % netgroup1), + ), + + + dict( + desc='Try to delete non-existent %r' % netgroup1, + command=('netgroup_del', [netgroup1], {}), + expected=errors.NotFound( + reason=u'%s: netgroup not found' % netgroup1), + ), + + + dict( + desc='Test an invalid netgroup name %r' % invalidnetgroup1, + command=('netgroup_add', [invalidnetgroup1], dict(description=u'Test')), + expected=errors.ValidationError(name='name', + error=u'may only include letters, numbers, _, -, and .'), + ), + + + dict( + desc='Test an invalid nisdomain1 name %r' % invalidnisdomain1, + command=('netgroup_add', [netgroup1], + dict(description=u'Test',nisdomainname=invalidnisdomain1)), + expected=errors.ValidationError(name='nisdomain', + error='may only include letters, numbers, _, -, and .'), + ), + + + dict( + desc='Test an invalid nisdomain2 name %r' % invalidnisdomain2, + command=('netgroup_add', [netgroup1], + dict(description=u'Test',nisdomainname=invalidnisdomain2)), + expected=errors.ValidationError(name='nisdomain', + error='may only include letters, numbers, _, -, and .'), + ), + + + dict( + desc='Create %r' % netgroup1, + command=('netgroup_add', [netgroup1], + dict(description=u'Test netgroup 1') + ), + expected=dict( + value=netgroup1, + summary=u'Added netgroup "%s"' % netgroup1, + result=dict( + dn=fuzzy_netgroupdn, + cn=[netgroup1], + objectclass=objectclasses.netgroup, + description=[u'Test netgroup 1'], + nisdomainname=['%s' % api.env.domain], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Create %r' % netgroup2, + command=('netgroup_add', [netgroup2], + dict(description=u'Test netgroup 2') + ), + expected=dict( + value=netgroup2, + summary=u'Added netgroup "%s"' % netgroup2, + result=dict( + dn=fuzzy_netgroupdn, + cn=[netgroup2], + objectclass=objectclasses.netgroup, + description=[u'Test netgroup 2'], + nisdomainname=['%s' % api.env.domain], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Create netgroup with name containing only one letter: %r' % netgroup_single, + command=('netgroup_add', [netgroup_single], + dict(description=u'Test netgroup_single') + ), + expected=dict( + value=netgroup_single, + summary=u'Added netgroup "%s"' % netgroup_single, + result=dict( + dn=fuzzy_netgroupdn, + cn=[netgroup_single], + objectclass=objectclasses.netgroup, + description=[u'Test netgroup_single'], + nisdomainname=['%s' % api.env.domain], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Delete %r' % netgroup_single, + command=('netgroup_del', [netgroup_single], {}), + expected=dict( + value=netgroup_single, + summary=u'Deleted netgroup "%s"' % netgroup_single, + result=dict(failed=u''), + ), + ), + + + dict( + desc='Try to create duplicate %r' % netgroup1, + command=('netgroup_add', [netgroup1], + dict(description=u'Test netgroup 1') + ), + expected=errors.DuplicateEntry( + message=u'netgroup with name "%s" already exists' % netgroup1), + ), + + + dict( + desc='Create host %r' % host1, + command=('host_add', [host1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=host1, + summary=u'Added host "%s"' % host1, + result=dict( + dn=host_dn1, + fqdn=[host1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (host1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[host1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % hostgroup1, + command=('hostgroup_add', [hostgroup1], + dict(description=u'Test hostgroup 1') + ), + expected=dict( + value=hostgroup1, + summary=u'Added hostgroup "%s"' % hostgroup1, + result=dict( + dn=hostgroup_dn1, + cn=[hostgroup1], + objectclass=objectclasses.hostgroup, + description=[u'Test hostgroup 1'], + mepmanagedentry=[DN(('cn',hostgroup1),('cn','ng'),('cn','alt'), + api.env.basedn)], + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Create %r' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/home/%s' % user1], + krbprincipalname=[u'%s@%s' % (user1, api.env.realm)], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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 %r' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2') + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/%s' % user2], + krbprincipalname=[u'%s@%s' % (user2, api.env.realm)], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user2, api.env.domain)], + displayname=[u'Test User2'], + cn=[u'Test User2'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[DN(('cn',user2),('cn','groups'),('cn','accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=DN(('uid',user2),('cn','users'),('cn','accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Create %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc 1') + ), + expected=dict( + value=group1, + summary=u'Added group "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc 1'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Add user %r to group %r' % (user2, group1), + command=( + 'group_add_member', [group1], dict(user=user2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn',group1),('cn','groups'),('cn','accounts'), + api.env.basedn), + 'member_user': (user2,), + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'Test desc 1'], + }, + ), + ), + + + dict( + desc='Add invalid host %r to netgroup %r' % (invalidhost, netgroup1), + command=('netgroup_add_member', [netgroup1], dict(host=invalidhost)), + expected=errors.ValidationError(name='host', + error='only letters, numbers, _, and - are allowed. ' + + u'DNS label may not start or end with -'), + ), + + + dict( + desc='Add host %r to netgroup %r' % (host1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(host=host1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add hostgroup %r to netgroup %r' % (hostgroup1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(hostgroup=hostgroup1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Search for netgroups using no_user', + command=('netgroup_find', [], dict(no_user=user1)), + expected=dict( + count=2, + truncated=False, + summary=u'2 netgroups matched', + result=[ + { + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + { + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup2], + 'description': [u'Test netgroup 2'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ], + ), + ), + + dict( + desc="Check %r doesn't match when searching for %s" % (netgroup1, user1), + command=('netgroup_find', [], dict(user=user1)), + expected=dict( + count=0, + truncated=False, + summary=u'0 netgroups matched', + result=[], + ), + ), + + dict( + desc='Add user %r to netgroup %r' % (user1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(user=user1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + dict( + desc="Check %r doesn't match when searching for no %s" % (netgroup1, user1), + command=('netgroup_find', [], dict(no_user=user1)), + expected=dict( + count=1, + truncated=False, + summary=u'1 netgroup matched', + result=[ + { + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup2], + 'description': [u'Test netgroup 2'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ], + ), + ), + + dict( + desc='Add group %r to netgroup %r' % (group1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(group=group1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add netgroup %r to netgroup %r' % (netgroup2, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(netgroup=netgroup2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add non-existent netgroup to netgroup %r' % (netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(netgroup=u'notfound') + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=[(u'notfound', u'no such entry')], + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add duplicate user %r to netgroup %r' % (user1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(user=user1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=[('%s' % user1, u'This entry is already a member')], + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + dict( + desc='Add duplicate group %r to netgroup %r' % (group1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(group=group1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=[('%s' % group1, u'This entry is already a member')], + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add duplicate host %r to netgroup %r' % (host1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(host=host1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=[('%s' % host1, u'This entry is already a member')], + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add duplicate hostgroup %r to netgroup %r' % (hostgroup1, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(hostgroup=hostgroup1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=[('%s' % hostgroup1, u'This entry is already a member')], + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ), + ), + + + dict( + desc='Add unknown host %r to netgroup %r' % (unknown_host, netgroup1), + command=( + 'netgroup_add_member', [netgroup1], dict(host=unknown_host) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + dict( + desc='Add invalid host %r to netgroup %r using setattr' % + (invalidhost, netgroup1), + command=( + 'netgroup_mod', [netgroup1], + dict(setattr='externalhost=%s' % invalidhost) + ), + expected=errors.ValidationError(name='externalhost', + error='only letters, numbers, _, and - are allowed. ' + + 'DNS label may not start or end with -'), + ), + + dict( + desc='Add unknown host %r to netgroup %r using addattr' % + (unknown_host2, netgroup1), + command=( + 'netgroup_mod', [netgroup1], + dict(addattr='externalhost=%s' % unknown_host2) + ), + expected=dict( + value=u'netgroup1', + summary=u'Modified netgroup "netgroup1"', + result={ + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host, unknown_host2], + }, + ) + ), + + dict( + desc='Remove unknown host %r from netgroup %r using delattr' % + (unknown_host2, netgroup1), + command=( + 'netgroup_mod', [netgroup1], + dict(delattr='externalhost=%s' % unknown_host2) + ), + expected=dict( + value=u'netgroup1', + summary=u'Modified netgroup "netgroup1"', + result={ + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ) + ), + + dict( + desc='Retrieve %r' % netgroup1, + command=('netgroup_show', [netgroup1], {}), + expected=dict( + value=netgroup1, + summary=None, + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + dict( + desc='Search for %r' % netgroup1, + command=('netgroup_find', [], dict(cn=netgroup1)), + expected=dict( + count=1, + truncated=False, + summary=u'1 netgroup matched', + result=[ + { + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ], + ), + ), + + dict( + desc='Search for %r using user' % netgroup1, + command=('netgroup_find', [], dict(user=user1)), + expected=dict( + count=1, + truncated=False, + summary=u'1 netgroup matched', + result=[ + { + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ], + ), + ), + + dict( + desc='Search for all netgroups using empty member user', + command=('netgroup_find', [], dict(user=None)), + expected=dict( + count=2, + truncated=False, + summary=u'2 netgroups matched', + result=[ + { + 'dn': fuzzy_netgroupdn, + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Test netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + { + 'dn': fuzzy_netgroupdn, + 'memberof_netgroup': (netgroup1,), + 'cn': [netgroup2], + 'description': [u'Test netgroup 2'], + 'nisdomainname': [u'%s' % api.env.domain], + }, + ], + ), + ), + + dict( + desc='Update %r' % netgroup1, + command=('netgroup_mod', [netgroup1], + dict(description=u'Updated netgroup 1') + ), + expected=dict( + value=netgroup1, + summary=u'Modified netgroup "%s"' % netgroup1, + result={ + 'memberhost_host': (host1,), + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove host %r from netgroup %r' % (host1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(host=host1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberhost_hostgroup': (hostgroup1,), + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove hostgroup %r from netgroup %r' % (hostgroup1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(hostgroup=hostgroup1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberuser_user': (user1,), + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove user %r from netgroup %r' % (user1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(user=user1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'memberuser_group': (group1,), + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove group %r from netgroup %r' % (group1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(group=group1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'member_netgroup': (netgroup2,), + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove netgroup %r from netgroup %r' % (netgroup2, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(netgroup=netgroup2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove host %r from netgroup %r again' % (host1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(host=host1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=[('%s' % host1, u'This entry is not a member')] + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove hostgroup %r from netgroup %r again' % (hostgroup1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(hostgroup=hostgroup1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=[('%s' % hostgroup1, u'This entry is not a member')], + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove user %r from netgroup %r again' % (user1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(user=user1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group=tuple(), + user=[('%s' % user1, u'This entry is not a member')], + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove group %r from netgroup %r again' % (group1, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(group=group1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=tuple(), + ), + memberuser=dict( + group= [('%s' % group1, u'This entry is not a member')], + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Remove netgroup %r from netgroup %r again' % (netgroup2, netgroup1), + command=( + 'netgroup_remove_member', [netgroup1], dict(netgroup=netgroup2) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + netgroup=[('%s' % netgroup2, u'This entry is not a member')], + ), + memberuser=dict( + group=tuple(), + user=tuple(), + ), + memberhost=dict( + hostgroup=tuple(), + host=tuple(), + ), + ), + result={ + 'dn': fuzzy_netgroupdn, + 'cn': [netgroup1], + 'description': [u'Updated netgroup 1'], + 'nisdomainname': [u'%s' % api.env.domain], + 'externalhost': [unknown_host], + }, + ), + ), + + + dict( + desc='Delete %r' % netgroup1, + command=('netgroup_del', [netgroup1], {}), + expected=dict( + value=netgroup1, + summary=u'Deleted netgroup "%s"' % netgroup1, + result=dict(failed=u''), + ), + ), + + ] + +# No way to convert this test just yet. + +# def test_6b_netgroup_show(self): +# """ +# Confirm the underlying triples +# """ +# # Do an LDAP query to the compat area and verify that the entry +# # is correct +# conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri, base_dn=api.env.basedn) +# conn.connect(ccache=ccache) +# try: +# entries = conn.find_entries('cn=%s' % self.ng_cn, +# base_dn='cn=ng,cn=compat,%s' % api.env.basedn) +# except errors.NotFound: +# raise nose.SkipTest('compat and nis are not enabled, skipping test') +# finally: +# conn.disconnect() +# triples = entries[0][0][1]['nisnetgrouptriple'] +# +# # This may not prove to be reliable since order is not guaranteed +# # and even which user gets into which triple can be random. +# assert '(nosuchhost,jexample,example.com)' in triples +# assert '(ipatesthost.%s,pexample,example.com)' % api.env.domain in triples diff --git a/ipatests/test_xmlrpc/test_passwd_plugin.py b/ipatests/test_xmlrpc/test_passwd_plugin.py new file mode 100644 index 000000000..2a44da711 --- /dev/null +++ b/ipatests/test_xmlrpc/test_passwd_plugin.py @@ -0,0 +1,69 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/passwd.py` module. +""" + +import sys + +from nose.tools import assert_raises # pylint: disable=E0611 + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors + + +class test_passwd(XMLRPC_test): + """ + Test the `passwd` plugin. + """ + uid = u'pwexample' + givenname = u'Jim' + sn = u'Example' + home = u'/home/%s' % uid + principalname = u'%s@%s' % (uid, api.env.realm) + kw = {'givenname': givenname, 'sn': sn, 'uid': uid, 'homedirectory': home} + + def test_1_user_add(self): + """ + Create a test user + """ + entry = api.Command['user_add'](**self.kw)['result'] + assert_attr_equal(entry, 'givenname', self.givenname) + assert_attr_equal(entry, 'sn', self.sn) + assert_attr_equal(entry, 'uid', self.uid) + assert_attr_equal(entry, 'homedirectory', self.home) + assert_attr_equal(entry, 'objectclass', 'ipaobject') + + def test_2_set_passwd(self): + """ + Test the `xmlrpc.passwd` method. + """ + out = api.Command['passwd'](self.uid, password=u'password1') + assert out['result'] is True + + def test_3_user_del(self): + """ + Remove the test user + """ + api.Command['user_del'](self.uid) + + # Verify that it is gone + with assert_raises(errors.NotFound): + api.Command['user_show'](self.uid) diff --git a/ipatests/test_xmlrpc/test_permission_plugin.py b/ipatests/test_xmlrpc/test_permission_plugin.py new file mode 100644 index 000000000..dbd9d6901 --- /dev/null +++ b/ipatests/test_xmlrpc/test_permission_plugin.py @@ -0,0 +1,972 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/permission.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +permission1 = u'testperm' +permission1_dn = DN(('cn',permission1), + api.env.container_permission,api.env.basedn) + + +permission1_renamed = u'testperm1_rn' +permission1_renamed_dn = DN(('cn',permission1_renamed), + api.env.container_permission,api.env.basedn) + +permission1_renamed_ucase = u'Testperm_RN' +permission1_renamed_ucase_dn = DN(('cn',permission1_renamed_ucase), + api.env.container_permission,api.env.basedn) + + +permission2 = u'testperm2' +permission2_dn = DN(('cn',permission2), + api.env.container_permission,api.env.basedn) + +permission3 = u'testperm3' +permission3_dn = DN(('cn',permission3), + api.env.container_permission,api.env.basedn) +permission3_attributelevelrights = { + 'member': u'rscwo', + 'seealso': u'rscwo', + 'ipapermissiontype': u'rscwo', + 'cn': u'rscwo', + 'businesscategory': u'rscwo', + 'objectclass': u'rscwo', + 'memberof': u'rscwo', + 'aci': u'rscwo', + 'subtree': u'rscwo', + 'o': u'rscwo', + 'filter': u'rscwo', + 'attrs': u'rscwo', + 'owner': u'rscwo', + 'group': u'rscwo', + 'ou': u'rscwo', + 'targetgroup': u'rscwo', + 'type': u'rscwo', + 'permissions': u'rscwo', + 'nsaccountlock': u'rscwo', + 'description': u'rscwo', + } + +privilege1 = u'testpriv1' +privilege1_dn = DN(('cn',privilege1), + api.env.container_privilege,api.env.basedn) + +invalid_permission1 = u'bad;perm' + + +class test_permission(Declarative): + + cleanup_commands = [ + ('permission_del', [permission1], {}), + ('permission_del', [permission2], {}), + ('permission_del', [permission3], {}), + ('privilege_del', [privilege1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % permission1, + command=('permission_show', [permission1], {}), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Try to update non-existent %r' % permission1, + command=('permission_mod', [permission1], dict(permissions=u'all')), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Try to delete non-existent %r' % permission1, + command=('permission_del', [permission1], {}), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Search for non-existent %r' % permission1, + command=('permission_find', [permission1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 permissions matched', + result=[], + ), + ), + + + dict( + desc='Create %r' % permission1, + command=( + 'permission_add', [permission1], dict( + type=u'user', + permissions=u'write', + ) + ), + expected=dict( + value=permission1, + summary=u'Added permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + objectclass=objectclasses.permission, + type=u'user', + permissions=[u'write'], + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % permission1, + command=( + 'permission_add', [permission1], dict( + type=u'user', + permissions=u'write', + ), + ), + expected=errors.DuplicateEntry(), + ), + + + dict( + desc='Create %r' % privilege1, + command=('privilege_add', [privilege1], + dict(description=u'privilege desc. 1') + ), + expected=dict( + value=privilege1, + summary=u'Added privilege "%s"' % privilege1, + result=dict( + dn=privilege1_dn, + cn=[privilege1], + description=[u'privilege desc. 1'], + objectclass=objectclasses.privilege, + ), + ), + ), + + + dict( + desc='Add permission %r to privilege %r' % (permission1, privilege1), + command=('privilege_add_permission', [privilege1], + dict(permission=permission1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Retrieve %r' % permission1, + command=('permission_show', [permission1], {}), + expected=dict( + value=permission1, + summary=None, + result={ + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + ), + ), + + + dict( + desc='Retrieve %r with --raw' % permission1, + command=('permission_show', [permission1], {'raw' : True}), + expected=dict( + value=permission1, + summary=None, + result={ + 'dn': permission1_dn, + 'cn': [permission1], + 'member': [privilege1_dn], + 'aci': u'(target = "ldap:///%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('uid', '*'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'testperm'), ('cn', 'permissions'), ('cn', 'pbac'), api.env.basedn)) + }, + ), + ), + + + dict( + desc='Search for %r' % permission1, + command=('permission_find', [permission1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + dict( + desc='Search for %r using --name' % permission1, + command=('permission_find', [], {'cn': permission1}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + dict( + desc='Search for non-existence permission using --name', + command=('permission_find', [], {'cn': u'notfound'}), + expected=dict( + count=0, + truncated=False, + summary=u'0 permissions matched', + result=[], + ), + ), + + + dict( + desc='Search for %r' % privilege1, + command=('permission_find', [privilege1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + dict( + desc='Search for %r with --raw' % permission1, + command=('permission_find', [permission1], {'raw' : True}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + 'member': [privilege1_dn], + 'aci': u'(target = "ldap:///%s")(version 3.0;acl "permission:testperm";allow (write) groupdn = "ldap:///%s";)' % \ + (DN(('uid', '*'), ('cn', 'users'), ('cn', 'accounts'), api.env.basedn), + DN(('cn', 'testperm'), ('cn', 'permissions'), ('cn', 'pbac'), api.env.basedn)), + }, + ], + ), + ), + + + dict( + desc='Create %r' % permission2, + command=( + 'permission_add', [permission2], dict( + type=u'user', + permissions=u'write', + setattr=u'owner=cn=test', + addattr=u'owner=cn=test2', + ) + ), + expected=dict( + value=permission2, + summary=u'Added permission "%s"' % permission2, + result=dict( + dn=permission2_dn, + cn=[permission2], + objectclass=objectclasses.permission, + type=u'user', + permissions=[u'write'], + owner=[u'cn=test', u'cn=test2'], + ), + ), + ), + + + dict( + desc='Search for %r' % permission1, + command=('permission_find', [permission1], {}), + expected=dict( + count=2, + truncated=False, + summary=u'2 permissions matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + { + 'dn': permission2_dn, + 'cn': [permission2], + 'type': u'user', + 'permissions': [u'write'], + }, + ], + ), + ), + + + dict( + desc='Search for %r with --pkey-only' % permission1, + command=('permission_find', [permission1], {'pkey_only' : True}), + expected=dict( + count=2, + truncated=False, + summary=u'2 permissions matched', + result=[ + { + 'dn': permission1_dn, + 'cn': [permission1], + }, + { + 'dn': permission2_dn, + 'cn': [permission2], + }, + ], + ), + ), + + + dict( + desc='Search by ACI attribute with --pkey-only', + command=('permission_find', [], {'pkey_only': True, + 'attrs': [u'krbminpwdlife']}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': DN(('cn','Modify Group Password Policy'), + api.env.container_permission, api.env.basedn), + 'cn': [u'Modify Group Password Policy'], + }, + ], + ), + ), + + + dict( + desc='Search for %r' % privilege1, + command=('privilege_find', [privilege1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 privilege matched', + result=[ + { + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + }, + ], + ), + ), + + + 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': 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': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + }, + { + 'dn': 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': DN(('cn', 'Modify HBAC rule'), + api.env.container_permission, api.env.basedn), + 'cn': [u'Modify HBAC rule'], + 'member_privilege': [u'HBAC Administrator'], + 'memberindirect_role': [u'IT Security Specialist'], + '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:///%s' % DN(('ipauniqueid', '*'), ('cn', 'hbac'), api.env.basedn), + }, + ], + ), + ), + + + dict( + desc='Update %r' % permission1, + command=( + 'permission_mod', [permission1], dict( + permissions=u'read', + memberof=u'ipausers', + setattr=u'owner=cn=other-test', + addattr=u'owner=cn=other-test2', + ) + ), + expected=dict( + value=permission1, + summary=u'Modified permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + member_privilege=[privilege1], + type=u'user', + permissions=[u'read'], + memberof=u'ipausers', + owner=[u'cn=other-test', u'cn=other-test2'], + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % permission1, + command=('permission_show', [permission1], {}), + expected=dict( + value=permission1, + summary=None, + result={ + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'read'], + 'memberof': u'ipausers', + }, + ), + ), + + + + dict( + desc='Try to rename %r to existing permission %r' % (permission1, + permission2), + command=( + 'permission_mod', [permission1], dict(rename=permission2, + permissions=u'all',) + ), + expected=errors.DuplicateEntry(), + ), + + + dict( + desc='Try to rename %r to empty name' % (permission1), + command=( + 'permission_mod', [permission1], dict(rename=u'', + permissions=u'all',) + ), + expected=errors.ValidationError(name='rename', + error=u'New name can not be empty'), + ), + + + dict( + desc='Check integrity of original permission %r' % permission1, + command=('permission_show', [permission1], {}), + expected=dict( + value=permission1, + summary=None, + result={ + 'dn': permission1_dn, + 'cn': [permission1], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'read'], + 'memberof': u'ipausers', + }, + ), + ), + + + dict( + desc='Rename %r to permission %r' % (permission1, + permission1_renamed), + command=( + 'permission_mod', [permission1], dict(rename=permission1_renamed, + permissions= u'all',) + ), + expected=dict( + value=permission1, + summary=u'Modified permission "%s"' % permission1, + result={ + 'dn': permission1_renamed_dn, + 'cn': [permission1_renamed], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'all'], + 'memberof': u'ipausers', + }, + ), + ), + + + dict( + desc='Rename %r to permission %r' % (permission1_renamed, + permission1_renamed_ucase), + command=( + 'permission_mod', [permission1_renamed], dict(rename=permission1_renamed_ucase, + permissions= u'write',) + ), + expected=dict( + value=permission1_renamed, + summary=u'Modified permission "%s"' % permission1_renamed, + result={ + 'dn': permission1_renamed_ucase_dn, + 'cn': [permission1_renamed_ucase], + 'member_privilege': [privilege1], + 'type': u'user', + 'permissions': [u'write'], + 'memberof': u'ipausers', + }, + ), + ), + + + dict( + desc='Change %r to a subtree type' % permission1_renamed_ucase, + command=( + 'permission_mod', [permission1_renamed_ucase], + dict(subtree=u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), + type=None) + ), + expected=dict( + value=permission1_renamed_ucase, + summary=u'Modified permission "%s"' % permission1_renamed_ucase, + result=dict( + dn=permission1_renamed_ucase_dn, + cn=[permission1_renamed_ucase], + member_privilege=[privilege1], + subtree=u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), + permissions=[u'write'], + memberof=u'ipausers', + ), + ), + ), + + + dict( + desc='Search for %r using --subtree' % permission1, + command=('permission_find', [], + {'subtree': u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn)}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn':permission1_renamed_ucase_dn, + 'cn':[permission1_renamed_ucase], + 'member_privilege':[privilege1], + 'subtree':u'ldap:///%s' % DN(('cn', '*'), ('cn', 'test'), ('cn', 'accounts'), api.env.basedn), + 'permissions':[u'write'], + 'memberof':u'ipausers', + }, + ], + ), + ), + + + dict( + desc='Search using nonexistent --subtree', + command=('permission_find', [], {'subtree': u'foo'}), + expected=dict( + count=0, + truncated=False, + summary=u'0 permissions matched', + result=[], + ), + ), + + + dict( + desc='Search using --targetgroup', + command=('permission_find', [], {'targetgroup': u'ipausers'}), + expected=dict( + count=1, + truncated=False, + summary=u'1 permission matched', + result=[ + { + 'dn': DN(('cn','Add user to default group'), + api.env.container_permission, api.env.basedn), + 'cn': [u'Add user to default group'], + 'member_privilege': [u'User Administrators'], + 'attrs': [u'member'], + 'targetgroup': u'ipausers', + 'memberindirect_role': [u'User Administrator'], + 'permissions': [u'write'] + } + ], + ), + ), + + + dict( + desc='Delete %r' % permission1_renamed_ucase, + command=('permission_del', [permission1_renamed_ucase], {}), + expected=dict( + result=dict(failed=u''), + value=permission1_renamed_ucase, + summary=u'Deleted permission "%s"' % permission1_renamed_ucase, + ) + ), + + + dict( + desc='Try to delete non-existent %r' % permission1, + command=('permission_del', [permission1], {}), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Try to retrieve non-existent %r' % permission1, + command=('permission_show', [permission1], {}), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Try to update non-existent %r' % permission1, + command=('permission_mod', [permission1], dict(rename=u'Foo')), + expected=errors.NotFound( + reason=u'%s: permission not found' % permission1), + ), + + + dict( + desc='Delete %r' % permission2, + command=('permission_del', [permission2], {}), + expected=dict( + result=dict(failed=u''), + value=permission2, + summary=u'Deleted permission "%s"' % permission2, + ) + ), + + + dict( + desc='Search for %r' % permission1, + command=('permission_find', [permission1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 permissions matched', + result=[], + ), + ), + + + dict( + desc='Delete %r' % privilege1, + command=('privilege_del', [privilege1], {}), + expected=dict( + result=dict(failed=u''), + value=privilege1, + summary=u'Deleted privilege "%s"' % privilege1, + ) + ), + + dict( + desc='Try to create permission %r with non-existing memberof' % permission1, + command=( + 'permission_add', [permission1], dict( + memberof=u'nonexisting', + permissions=u'write', + ) + ), + expected=errors.NotFound(reason=u'nonexisting: group not found'), + ), + + dict( + desc='Create memberof permission %r' % permission1, + command=( + 'permission_add', [permission1], dict( + memberof=u'editors', + permissions=u'write', + type=u'user', + ) + ), + expected=dict( + value=permission1, + summary=u'Added permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + objectclass=objectclasses.permission, + memberof=u'editors', + permissions=[u'write'], + type=u'user', + ), + ), + ), + + dict( + desc='Try to update non-existent memberof of %r' % permission1, + command=('permission_mod', [permission1], dict( + memberof=u'nonexisting')), + expected=errors.NotFound(reason=u'nonexisting: group not found'), + ), + + dict( + desc='Update memberof permission %r' % permission1, + command=( + 'permission_mod', [permission1], dict( + memberof=u'admins', + ) + ), + expected=dict( + value=permission1, + summary=u'Modified permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + memberof=u'admins', + permissions=[u'write'], + type=u'user', + ), + ), + ), + + dict( + desc='Unset memberof of permission %r' % permission1, + command=( + 'permission_mod', [permission1], dict( + memberof=None, + ) + ), + expected=dict( + summary=u'Modified permission "%s"' % permission1, + value=permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + permissions=[u'write'], + type=u'user', + ), + ), + ), + + + dict( + desc='Delete %r' % permission1, + command=('permission_del', [permission1], {}), + expected=dict( + result=dict(failed=u''), + value=permission1, + summary=u'Deleted permission "%s"' % permission1, + ) + ), + + + dict( + desc='Create targetgroup permission %r' % permission1, + command=( + 'permission_add', [permission1], dict( + targetgroup=u'editors', + permissions=u'write', + ) + ), + expected=dict( + value=permission1, + summary=u'Added permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + objectclass=objectclasses.permission, + targetgroup=u'editors', + permissions=[u'write'], + ), + ), + ), + + dict( + desc='Try to create invalid %r' % invalid_permission1, + command=('permission_add', [invalid_permission1], dict( + type=u'user', + permissions=u'write', + )), + expected=errors.ValidationError(name='name', + error='May only contain letters, numbers, -, _, and space'), + ), + + dict( + desc='Create %r' % permission3, + command=( + 'permission_add', [permission3], dict( + type=u'user', + permissions=u'write', + attrs=[u'cn'] + ) + ), + expected=dict( + value=permission3, + summary=u'Added permission "%s"' % permission3, + result=dict( + dn=permission3_dn, + cn=[permission3], + objectclass=objectclasses.permission, + type=u'user', + permissions=[u'write'], + attrs=(u'cn',), + ), + ), + ), + + dict( + desc='Retrieve %r with --all --rights' % permission3, + command=('permission_show', [permission3], {'all' : True, 'rights' : True}), + expected=dict( + value=permission3, + summary=None, + result=dict( + dn=permission3_dn, + cn=[permission3], + objectclass=objectclasses.permission, + type=u'user', + attrs=(u'cn',), + permissions=[u'write'], + attributelevelrights=permission3_attributelevelrights + ), + ), + ), + + dict( + desc='Modify %r with --all -rights' % permission3, + command=('permission_mod', [permission3], {'all' : True, 'rights': True, 'attrs':[u'cn',u'uid']}), + expected=dict( + value=permission3, + summary=u'Modified permission "%s"' % permission3, + result=dict( + dn=permission3_dn, + cn=[permission3], + objectclass=objectclasses.permission, + type=u'user', + attrs=(u'cn',u'uid'), + permissions=[u'write'], + attributelevelrights=permission3_attributelevelrights, + ), + ), + ), + ] diff --git a/ipatests/test_xmlrpc/test_ping_plugin.py b/ipatests/test_xmlrpc/test_ping_plugin.py new file mode 100644 index 000000000..1d401993a --- /dev/null +++ b/ipatests/test_xmlrpc/test_ping_plugin.py @@ -0,0 +1,53 @@ +# Authors: +# Petr Viktorin <pviktori@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/ping.py` module, and XML-RPC in general. +""" + +from ipalib import api, errors, messages, _ +from ipatests.util import Fuzzy +from xmlrpc_test import Declarative +from ipapython.version import API_VERSION + + +class test_ping(Declarative): + + tests = [ + dict( + desc='Ping the server', + command=('ping', [], {}), + expected=dict( + summary=Fuzzy('IPA server version .*. API version .*')), + ), + + dict( + desc='Try to ping with an argument', + command=('ping', ['bad_arg'], {}), + expected=errors.ZeroArgumentError(name='ping'), + ), + + dict( + desc='Try to ping with an option', + command=('ping', [], dict(bad_arg=True)), + expected=errors.OptionError(_('Unknown option: %(option)s'), + option='bad_arg'), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_privilege_plugin.py b/ipatests/test_xmlrpc/test_privilege_plugin.py new file mode 100644 index 000000000..741590dd0 --- /dev/null +++ b/ipatests/test_xmlrpc/test_privilege_plugin.py @@ -0,0 +1,412 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/privilege.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +permission1 = u'testperm' +permission1_dn = DN(('cn',permission1), + api.env.container_permission,api.env.basedn) + +permission2 = u'testperm2' +permission2_dn = DN(('cn',permission2), + api.env.container_permission,api.env.basedn) + +privilege1 = u'testpriv1' +privilege1_dn = DN(('cn',privilege1), + api.env.container_privilege,api.env.basedn) + + +class test_privilege(Declarative): + + cleanup_commands = [ + ('permission_del', [permission1], {}), + ('permission_del', [permission2], {}), + ('privilege_del', [privilege1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % privilege1, + command=('privilege_show', [privilege1], {}), + expected=errors.NotFound( + reason=u'%s: privilege not found' % privilege1), + ), + + + dict( + desc='Try to update non-existent %r' % privilege1, + command=('privilege_mod', [privilege1], dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: privilege not found' % privilege1), + ), + + + dict( + desc='Try to delete non-existent %r' % privilege1, + command=('privilege_del', [privilege1], {}), + expected=errors.NotFound( + reason=u'%s: privilege not found' % privilege1), + ), + + + dict( + desc='Search for non-existent %r' % privilege1, + command=('privilege_find', [privilege1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 privileges matched', + result=[], + ), + ), + + + dict( + desc='Create %r' % permission1, + command=( + 'permission_add', [permission1], dict( + type=u'user', + permissions=[u'add', u'delete'], + ) + ), + expected=dict( + value=permission1, + summary=u'Added permission "%s"' % permission1, + result=dict( + dn=permission1_dn, + cn=[permission1], + objectclass=objectclasses.permission, + type=u'user', + permissions=[u'add', u'delete'], + ), + ), + ), + + + dict( + desc='Create %r' % privilege1, + command=('privilege_add', [privilege1], + dict(description=u'privilege desc. 1') + ), + expected=dict( + value=privilege1, + summary=u'Added privilege "%s"' % privilege1, + result=dict( + dn=privilege1_dn, + cn=[privilege1], + description=[u'privilege desc. 1'], + objectclass=objectclasses.privilege, + ), + ), + ), + + + dict( + desc='Add permission %r to privilege %r' % (permission1, privilege1), + command=('privilege_add_permission', [privilege1], + dict(permission=permission1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Retrieve %r' % privilege1, + command=('privilege_show', [privilege1], {}), + expected=dict( + value=privilege1, + summary=None, + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + }, + ), + ), + + + dict( + desc='Search for %r' % privilege1, + command=('privilege_find', [privilege1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 privilege matched', + result=[ + { + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + }, + ], + ), + ), + + + dict( + desc='Search for %r' % privilege1, + command=('privilege_find', [privilege1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 privilege matched', + result=[ + { + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1], + }, + ], + ), + ), + + + dict( + desc='Create %r' % permission2, + command=( + 'permission_add', [permission2], dict( + type=u'user', + permissions=u'write', + ) + ), + expected=dict( + value=permission2, + summary=u'Added permission "%s"' % permission2, + result=dict( + dn=permission2_dn, + cn=[permission2], + objectclass=objectclasses.permission, + type=u'user', + permissions=[u'write'], + ), + ), + ), + + + dict( + desc='Add permission %r to privilege %r' % (permission2, privilege1), + command=('privilege_add_permission', [privilege1], + dict(permission=permission2) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1, permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Add permission %r to privilege %r again' % (permission2, privilege1), + command=('privilege_add_permission', [privilege1], + dict(permission=permission2) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + permission=[(u'testperm2', u'This entry is already a member'),], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1, permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Search for %r' % privilege1, + command=('privilege_find', [privilege1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 privilege matched', + result=[ + { + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'privilege desc. 1'], + 'memberof_permission': [permission1, permission2], + }, + ], + ), + ), + + + dict( + desc='Update %r' % privilege1, + command=( + 'privilege_mod', [privilege1], dict(description=u'New desc 1') + ), + expected=dict( + value=privilege1, + summary=u'Modified privilege "%s"' % privilege1, + result=dict( + cn=[privilege1], + description=[u'New desc 1'], + memberof_permission=[permission1, permission2], + ), + ), + ), + + + dict( + desc='Remove permission from %r' % privilege1, + command=('privilege_remove_permission', [privilege1], + dict(permission=permission1), + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'New desc 1'], + 'memberof_permission': [permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Remove permission from %r again' % privilege1, + command=('privilege_remove_permission', [privilege1], + dict(permission=permission1), + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + permission=[(u'testperm', u'This entry is not a member'),], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'New desc 1'], + 'memberof_permission': [permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Add zero permissions to %r' % privilege1, + command=('privilege_add_permission', [privilege1], + dict(permission=None), + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'New desc 1'], + 'memberof_permission': [permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Remove zero permissions from %r' % privilege1, + command=('privilege_remove_permission', [privilege1], + dict(permission=None), + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + permission=[], + ), + ), + result={ + 'dn': privilege1_dn, + 'cn': [privilege1], + 'description': [u'New desc 1'], + 'memberof_permission': [permission2], + 'objectclass': objectclasses.privilege, + } + ), + ), + + + dict( + desc='Delete %r' % privilege1, + command=('privilege_del', [privilege1], {}), + expected=dict( + result=dict(failed=u''), + value=privilege1, + summary=u'Deleted privilege "%s"' % privilege1, + ) + ), + + + ] diff --git a/ipatests/test_xmlrpc/test_pwpolicy_plugin.py b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py new file mode 100644 index 000000000..3b482ce2d --- /dev/null +++ b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py @@ -0,0 +1,244 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/pwpolicy.py` module. +""" + +import sys +from nose.tools import assert_raises # pylint: disable=E0611 + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors + + +class test_pwpolicy(XMLRPC_test): + """ + Test the `pwpolicy` plugin. + """ + group = u'testgroup12' + group2 = u'testgroup22' + group3 = u'testgroup32' + user = u'testuser12' + kw = {'cospriority': 1, 'krbminpwdlife': 30, 'krbmaxpwdlife': 40, 'krbpwdhistorylength': 5, 'krbpwdminlength': 6 } + kw2 = {'cospriority': 2, 'krbminpwdlife': 40, 'krbmaxpwdlife': 60, 'krbpwdhistorylength': 8, 'krbpwdminlength': 9 } + kw3 = {'cospriority': 10, 'krbminpwdlife': 50, 'krbmaxpwdlife': 30, 'krbpwdhistorylength': 3, 'krbpwdminlength': 4 } + global_policy = u'global_policy' + + def test_1_pwpolicy_add(self): + """ + Test adding a per-group policy using the `xmlrpc.pwpolicy_add` method. + """ + # First set up a group and user that will use this policy + self.failsafe_add( + api.Object.group, self.group, description=u'pwpolicy test group', + ) + self.failsafe_add( + api.Object.user, self.user, givenname=u'Test', sn=u'User' + ) + api.Command.group_add_member(self.group, user=self.user) + + entry = api.Command['pwpolicy_add'](self.group, **self.kw)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '30') + assert_attr_equal(entry, 'krbmaxpwdlife', '40') + assert_attr_equal(entry, 'krbpwdhistorylength', '5') + assert_attr_equal(entry, 'krbpwdminlength', '6') + assert_attr_equal(entry, 'cospriority', '1') + + def test_2_pwpolicy_add(self): + """ + Add a policy with a already used priority. + + The priority validation is done first, so it's OK that the group + is the same here. + """ + try: + api.Command['pwpolicy_add'](self.group, **self.kw) + except errors.ValidationError: + pass + else: + assert False + + def test_3_pwpolicy_add(self): + """ + Add a policy that already exists. + """ + try: + # cospriority needs to be unique + self.kw['cospriority'] = 3 + api.Command['pwpolicy_add'](self.group, **self.kw) + except errors.DuplicateEntry: + pass + else: + assert False + + def test_4_pwpolicy_add(self): + """ + Test adding another per-group policy using the `xmlrpc.pwpolicy_add` method. + """ + self.failsafe_add( + api.Object.group, self.group2, description=u'pwpolicy test group 2' + ) + entry = api.Command['pwpolicy_add'](self.group2, **self.kw2)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '40') + assert_attr_equal(entry, 'krbmaxpwdlife', '60') + assert_attr_equal(entry, 'krbpwdhistorylength', '8') + assert_attr_equal(entry, 'krbpwdminlength', '9') + assert_attr_equal(entry, 'cospriority', '2') + + def test_5_pwpolicy_add(self): + """ + Add a pwpolicy for a non-existent group + """ + try: + api.Command['pwpolicy_add'](u'nopwpolicy', cospriority=1, krbminpwdlife=1) + except errors.NotFound: + pass + else: + assert False + + def test_6_pwpolicy_show(self): + """ + Test the `xmlrpc.pwpolicy_show` method with global policy. + """ + entry = api.Command['pwpolicy_show']()['result'] + # Note that this assumes an unchanged global policy + assert_attr_equal(entry, 'krbminpwdlife', '1') + assert_attr_equal(entry, 'krbmaxpwdlife', '90') + assert_attr_equal(entry, 'krbpwdhistorylength', '0') + assert_attr_equal(entry, 'krbpwdminlength', '8') + + def test_7_pwpolicy_show(self): + """ + Test the `xmlrpc.pwpolicy_show` method. + """ + entry = api.Command['pwpolicy_show'](self.group)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '30') + assert_attr_equal(entry, 'krbmaxpwdlife', '40') + assert_attr_equal(entry, 'krbpwdhistorylength', '5') + assert_attr_equal(entry, 'krbpwdminlength', '6') + assert_attr_equal(entry, 'cospriority', '1') + + def test_8_pwpolicy_mod(self): + """ + Test the `xmlrpc.pwpolicy_mod` method for global policy. + """ + entry = api.Command['pwpolicy_mod'](krbminpwdlife=50)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '50') + + # Great, now change it back + entry = api.Command['pwpolicy_mod'](krbminpwdlife=1)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '1') + + def test_9_pwpolicy_mod(self): + """ + Test the `xmlrpc.pwpolicy_mod` method. + """ + entry = api.Command['pwpolicy_mod'](self.group, krbminpwdlife=50)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '50') + + def test_a_pwpolicy_managed(self): + """ + Test adding password policy to a managed group. + """ + try: + entry = api.Command['pwpolicy_add'](self.user, krbminpwdlife=50, cospriority=2)['result'] + except errors.ManagedPolicyError: + pass + else: + assert False + + def test_b_pwpolicy_add(self): + """ + Test adding a third per-group policy using the `xmlrpc.pwpolicy_add` method. + """ + self.failsafe_add( + api.Object.group, self.group3, description=u'pwpolicy test group 3' + ) + entry = api.Command['pwpolicy_add'](self.group3, **self.kw3)['result'] + assert_attr_equal(entry, 'krbminpwdlife', '50') + assert_attr_equal(entry, 'krbmaxpwdlife', '30') + assert_attr_equal(entry, 'krbpwdhistorylength', '3') + assert_attr_equal(entry, 'krbpwdminlength', '4') + assert_attr_equal(entry, 'cospriority', '10') + + def test_c_pwpolicy_find(self): + """Test that password policies are sorted and reported properly""" + result = api.Command['pwpolicy_find']()['result'] + assert len(result) == 4 + + # Test that policies are sorted in numerical order + assert result[0]['cn'] == (self.group,) + assert result[1]['cn'] == (self.group2,) + assert result[2]['cn'] == (self.group3,) + assert result[3]['cn'] == ('global_policy',) + + # Test that returned values match the arguments + # Only test the second and third results; the first one was modified + for entry, expected in (result[1], self.kw2), (result[2], self.kw3): + for name, value in expected.iteritems(): + assert_attr_equal(entry, name, str(value)) + + def test_c_pwpolicy_find_pkey_only(self): + """Test that password policies are sorted properly with --pkey-only""" + result = api.Command['pwpolicy_find'](pkey_only=True)['result'] + assert len(result) == 4 + assert result[0]['cn'] == (self.group,) + assert result[1]['cn'] == (self.group2,) + assert result[2]['cn'] == (self.group3,) + assert result[3]['cn'] == ('global_policy',) + + def test_d_pwpolicy_show(self): + """Test that deleting a group removes its pwpolicy""" + api.Command['group_del'](self.group3) + with assert_raises(errors.NotFound): + api.Command['pwpolicy_show'](self.group3) + + def test_e_pwpolicy_del(self): + """ + Test the `xmlrpc.pwpolicy_del` method. + """ + api.Command['pwpolicy_del'](self.group) + # Verify that it is gone + try: + api.Command['pwpolicy_show'](self.group) + except errors.NotFound: + pass + else: + assert False + + # Verify that global policy cannot be deleted + try: + api.Command['pwpolicy_del'](self.global_policy) + except errors.ValidationError: + pass + else: + assert False + try: + api.Command['pwpolicy_show'](self.global_policy) + except errors.NotFound: + assert False + + # Remove the groups we created + api.Command['group_del'](self.group) + api.Command['group_del'](self.group2) + + # Remove the user we created + api.Command['user_del'](self.user) diff --git a/ipatests/test_xmlrpc/test_range_plugin.py b/ipatests/test_xmlrpc/test_range_plugin.py new file mode 100644 index 000000000..cbb700e99 --- /dev/null +++ b/ipatests/test_xmlrpc/test_range_plugin.py @@ -0,0 +1,536 @@ +# Authors: +# Alexander Bokovoy <abokovoy@redhat.com> +# +# Copyright (C) 2012 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +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 ipatests.test_xmlrpc import objectclasses +from ipapython.dn import * + +import ldap, ldap.sasl, ldap.modlist + +testrange1 = u'testrange1' +testrange1_base_id = 900000 +testrange1_size = 99999 +testrange1_base_rid = 10000 +testrange1_secondary_base_rid = 200000 + +testrange2 = u'testrange2' +testrange2_base_id = 100 +testrange2_size = 50 +testrange2_base_rid = 100 +testrange2_secondary_base_rid = 1000 + +testrange3 = u'testrange3' +testrange3_base_id = 200 +testrange3_size = 50 +testrange3_base_rid = 70 +testrange3_secondary_base_rid = 1100 + +testrange4 = u'testrange4' +testrange4_base_id = 300 +testrange4_size = 50 +testrange4_base_rid = 200 +testrange4_secondary_base_rid = 1030 + +testrange5 = u'testrange5' +testrange5_base_id = 400 +testrange5_size = 50 +testrange5_base_rid = 1020 +testrange5_secondary_base_rid = 1200 + +testrange6 = u'testrange6' +testrange6_base_id = 130 +testrange6_size = 50 +testrange6_base_rid = 500 +testrange6_secondary_base_rid = 1300 + +testrange7 = u'testrange7' +testrange7_base_id = 600 +testrange7_size = 50 +testrange7_base_rid = 600 +testrange7_secondary_base_rid = 649 + +testrange8 = u'testrange8' +testrange8_base_id = 700 +testrange8_size = 50 +testrange8_base_rid = 700 + +testrange9 = u'testrange9' +testrange9_base_id = 800 +testrange9_size = 50 +testrange9_base_rid = 800 + +testrange10 = u'testrange10' +testrange10_base_id = 900 +testrange10_size = 50 +testrange10_base_rid = 900 + +testrange9_dn = "cn={name},cn=ranges,cn=etc,{basedn}".format(name=testrange9, + basedn=api.env.basedn) + +testrange9_add = dict( + objectClass=["ipaIDrange", "ipatrustedaddomainrange"], + ipaBaseID="{base_id}".format(base_id=testrange9_base_id), + ipaBaseRID="{base_rid}".format(base_rid=testrange9_base_rid), + ipaIDRangeSize="{size}".format(size=testrange9_size), + ipaNTTrustedDomainSID="S-1-5-21-259319770-2312917334-591429603", + ) + +testrange10_dn = "cn={name},cn=ranges,cn=etc,{basedn}".format(name=testrange10, + basedn=api.env.basedn) + +testrange10_add = dict( + objectClass=["ipaIDrange", "ipatrustedaddomainrange"], + ipaBaseID="{base_id}".format(base_id=testrange10_base_id), + ipaBaseRID="{base_rid}".format(base_rid=testrange10_base_rid), + ipaIDRangeSize="{size}".format(size=testrange10_size), + ipaNTTrustedDomainSID="S-1-5-21-2997650941-1802118864-3094776726", + ) + +testtrust = u'testtrust' +testtrust_dn = "cn=testtrust,cn=trusts,{basedn}".format(basedn=api.env.basedn) + +testtrust_add = dict( + objectClass=["ipaNTTrustedDomain", "ipaIDobject", "top"], + ipaNTFlatName='TESTTRUST', + ipaNTTrustedDomainSID='S-1-5-21-2997650941-1802118864-3094776726', + ipaNTSIDBlacklistIncoming='S-1-0', + ipaNTTrustPartner='testtrust.mock', + ipaNTTrustType='2', + ipaNTTrustDirection='3', + ipaNTTrustAttributes='8', + ) + +user1 = u'tuser1' +user1_uid = 900000 +group1 = u'group1' +group1_gid = 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() + + @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() + + cleanup_commands = [ + ('idrange_del', [testrange1, testrange2, testrange3, testrange4, + testrange5, testrange6, testrange7, testrange8], + {'continue': True}), + ('user_del', [user1], {}), + ('group_del', [group1], {}), + ] + + tests = [ + dict( + desc='Create ID range %r' % (testrange1), + command=('idrange_add', [testrange1], + dict(ipabaseid=testrange1_base_id, ipaidrangesize=testrange1_size, + ipabaserid=testrange1_base_rid, ipasecondarybaserid=testrange1_secondary_base_rid)), + expected=dict( + result=dict( + dn=DN(('cn',testrange1),('cn','ranges'),('cn','etc'), + api.env.basedn), + cn=[testrange1], + objectclass=[u'ipaIDrange', u'ipadomainidrange'], + ipabaseid=[unicode(testrange1_base_id)], + ipabaserid=[unicode(testrange1_base_rid)], + ipasecondarybaserid=[unicode(testrange1_secondary_base_rid)], + ipaidrangesize=[unicode(testrange1_size)], + iparangetype=[u'local domain range'], + ), + value=testrange1, + summary=u'Added ID range "%s"' % (testrange1), + ), + ), + + dict( + desc='Retrieve ID range %r' % (testrange1), + command=('idrange_show', [testrange1], dict()), + expected=dict( + result=dict( + dn=DN(('cn',testrange1),('cn','ranges'),('cn','etc'), + api.env.basedn), + cn=[testrange1], + ipabaseid=[unicode(testrange1_base_id)], + ipabaserid=[unicode(testrange1_base_rid)], + ipasecondarybaserid=[unicode(testrange1_secondary_base_rid)], + ipaidrangesize=[unicode(testrange1_size)], + iparangetype=[u'local domain range'], + ), + value=testrange1, + summary=None, + ), + ), + + + dict( + desc='Create user %r in ID 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'], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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 ID 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 ID range %r to get out bounds object #1' % (testrange1), + command=('idrange_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 ID range %r to get out bounds object #2' % (testrange1), + command=('idrange_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 ID range %r to get out bounds object #3' % (testrange1), + command=('idrange_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 ID range %r' % (testrange1), + command=('idrange_mod', [testrange1], dict(ipaidrangesize=90000)), + expected=dict( + result=dict( + cn=[testrange1], + ipabaseid=[unicode(testrange1_base_id)], + ipabaserid=[unicode(testrange1_base_rid)], + ipasecondarybaserid=[unicode(testrange1_secondary_base_rid)], + ipaidrangesize=[u'90000'], + iparangetype=[u'local domain range'], + ), + value=testrange1, + summary=u'Modified ID range "%s"' % (testrange1), + ), + ), + + + dict( + desc='Try to delete ID range %r with active IDs inside it' % testrange1, + command=('idrange_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 ID range %r' % testrange1, + command=('idrange_del', [testrange1], {}), + expected=dict( + result=dict(failed=u''), + value=testrange1, + summary=u'Deleted ID range "%s"' % testrange1, + ), + ), + + dict( + desc='Create ID range %r' % (testrange2), + command=('idrange_add', [testrange2], + dict(ipabaseid=testrange2_base_id, + ipaidrangesize=testrange2_size, + ipabaserid=testrange2_base_rid, + ipasecondarybaserid=testrange2_secondary_base_rid)), + expected=dict( + result=dict( + dn=DN(('cn',testrange2),('cn','ranges'),('cn','etc'), + api.env.basedn), + cn=[testrange2], + objectclass=[u'ipaIDrange', u'ipadomainidrange'], + ipabaseid=[unicode(testrange2_base_id)], + ipabaserid=[unicode(testrange2_base_rid)], + ipasecondarybaserid=[unicode(testrange2_secondary_base_rid)], + ipaidrangesize=[unicode(testrange2_size)], + iparangetype=[u'local domain range'], + ), + value=testrange2, + summary=u'Added ID range "%s"' % (testrange2), + ), + ), + + dict( + desc='Try to modify ID range %r so that its rid ranges are overlapping themselves' % (testrange2), + command=('idrange_mod', [testrange2], + dict(ipabaserid=(testrange2_base_rid*10))), + expected=errors.ValidationError( + name='ID Range setup', error='Primary RID range and secondary RID range cannot overlap'), + ), + + dict( + desc='Try to create ID range %r with overlapping rid range' % (testrange3), + command=('idrange_add', [testrange3], + dict(ipabaseid=testrange3_base_id, + ipaidrangesize=testrange3_size, + ipabaserid=testrange3_base_rid, + ipasecondarybaserid=testrange3_secondary_base_rid)), + expected=errors.DatabaseError( + desc='Constraint violation', info='New primary rid range overlaps with existing primary rid range.'), + ), + + dict( + desc='Try to create ID range %r with overlapping secondary rid range' % (testrange4), + command=('idrange_add', [testrange4], + dict(ipabaseid=testrange4_base_id, + ipaidrangesize=testrange4_size, + ipabaserid=testrange4_base_rid, + ipasecondarybaserid=testrange4_secondary_base_rid)), + expected=errors.DatabaseError( + desc='Constraint violation', info='New secondary rid range overlaps with existing secondary rid range.'), + ), + + dict( + desc='Try to create ID range %r with primary range overlapping secondary rid range' % (testrange5), + command=('idrange_add', [testrange5], + dict(ipabaseid=testrange5_base_id, + ipaidrangesize=testrange5_size, + ipabaserid=testrange5_base_rid, + ipasecondarybaserid=testrange5_secondary_base_rid)), + expected=errors.DatabaseError( + desc='Constraint violation', info='New primary rid range overlaps with existing secondary rid range.'), + ), + + dict( + desc='Try to create ID range %r with overlapping id range' % (testrange6), + command=('idrange_add', [testrange6], + dict(ipabaseid=testrange6_base_id, + ipaidrangesize=testrange6_size, + ipabaserid=testrange6_base_rid, + ipasecondarybaserid=testrange6_secondary_base_rid)), + expected=errors.DatabaseError( + desc='Constraint violation', info='New base range overlaps with existing base range.'), + ), + + dict( + desc='Try to create ID range %r with rid ranges overlapping themselves' % (testrange7), + command=('idrange_add', [testrange7], + dict(ipabaseid=testrange7_base_id, + ipaidrangesize=testrange7_size, + ipabaserid=testrange7_base_rid, + ipasecondarybaserid=testrange7_secondary_base_rid)), + expected=errors.ValidationError( + name='ID Range setup', error='Primary RID range and secondary RID range cannot overlap'), + ), + + dict( + desc='Delete ID range %r' % testrange2, + command=('idrange_del', [testrange2], {}), + expected=dict( + result=dict(failed=u''), + value=testrange2, + summary=u'Deleted ID range "%s"' % testrange2, + ), + ), + + dict( + desc='Create ID range %r' % (testrange8), + command=('idrange_add', [testrange8], + dict(ipabaseid=testrange8_base_id, + ipaidrangesize=testrange8_size)), + expected=dict( + result=dict( + dn=DN(('cn',testrange8),('cn','ranges'),('cn','etc'), + api.env.basedn), + cn=[testrange8], + objectclass=[u'ipaIDrange', u'ipadomainidrange'], + ipabaseid=[unicode(testrange8_base_id)], + ipaidrangesize=[unicode(testrange8_size)], + iparangetype=[u'local domain range'], + ), + value=testrange8, + summary=u'Added ID range "%s"' % (testrange8), + ), + ), + + 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( + result=dict(failed=u''), + value=testrange8, + summary=u'Deleted ID range "%s"' % testrange8, + ), + ), + + dict( + desc='Delete non-active AD trusted range %r' % testrange9, + command=('idrange_del', [testrange9], {}), + expected=dict( + result=dict(failed=u''), + value=testrange9, + summary=u'Deleted ID range "%s"' % testrange9, + ), + ), + + dict( + desc='Try to delete active AD trusted range %r' % testrange10, + command=('idrange_del', [testrange10], {}), + expected=errors.DependentEntry( + label='Active Trust', + key=testrange10, + dependent=testtrust), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_realmdomains_plugin.py b/ipatests/test_xmlrpc/test_realmdomains_plugin.py new file mode 100644 index 000000000..8abb53e48 --- /dev/null +++ b/ipatests/test_xmlrpc/test_realmdomains_plugin.py @@ -0,0 +1,164 @@ +# Authors: +# Ana Krivokapic <akrivoka@redhat.com> +# +# Copyright (C) 2013 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/realmdomains.py` module. +""" + +from ipalib import api, errors +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative + + +cn = u'Realm Domains' +dn = DN(('cn', cn), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) +our_domain = api.env.domain +new_domain_1 = u'example1.com' +new_domain_2 = u'example2.com' +bad_domain = u'doesnotexist.test' + + +class test_realmdomains(Declarative): + + cleanup_commands = [ + ('realmdomains_mod', [], {'associateddomain': [our_domain]}), + ] + + tests = [ + dict( + desc='Retrieve realm domains', + command=('realmdomains_show', [], {}), + expected=dict( + value=u'', + summary=None, + result=dict( + dn=dn, + associateddomain=[our_domain], + ), + ), + ), + dict( + desc='Retrieve realm domains - print all attributes', + command=('realmdomains_show', [], {'all': True}), + expected=dict( + value=u'', + summary=None, + result=dict( + dn=dn, + associateddomain=[our_domain], + cn=[cn], + objectclass=objectclasses.realmdomains, + ), + ), + ), + dict( + desc='Replace list of realm domains with "%s"' % [our_domain, new_domain_1], + command=('realmdomains_mod', [], {'associateddomain': [our_domain, new_domain_1]}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1], + ), + ), + ), + dict( + desc='Add domain "%s" to list' % new_domain_2, + command=('realmdomains_mod', [], {'add_domain': new_domain_2}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1, new_domain_2], + ), + ), + ), + dict( + desc='Delete domain "%s" from list' % new_domain_2, + command=('realmdomains_mod', [], {'del_domain': new_domain_2}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_1], + ), + ), + ), + dict( + desc='Add domain "%s" and delete domain "%s"' % (new_domain_2, new_domain_1), + command=('realmdomains_mod', [], {'add_domain': new_domain_2, 'del_domain': new_domain_1}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_2], + ), + ), + ), + dict( + desc='Try to specify --domain and --add-domain options together', + command=('realmdomains_mod', [], { + 'associateddomain': [our_domain, new_domain_1], + 'add_domain': new_domain_1, + }), + expected=errors.MutuallyExclusiveError( + reason='you cannot specify the --domain option together with --add-domain or --del-domain'), + ), + dict( + desc='Try to replace list of realm domains with a list without our domain', + command=('realmdomains_mod', [], {'associateddomain': [new_domain_1]}), + expected=errors.ValidationError( + name='domain', error='cannot delete domain of IPA server'), + ), + dict( + desc='Try to replace list of realm domains with a list with an invalid domain "%s"' % bad_domain, + command=('realmdomains_mod', [], {'associateddomain': [our_domain, bad_domain]}), + expected=errors.ValidationError( + name='domain', error='no SOA or NS records found for domains: %s' % bad_domain), + ), + dict( + desc='Try to add an invalid domain "%s"' % bad_domain, + command=('realmdomains_mod', [], {'add_domain': bad_domain}), + expected=errors.ValidationError( + name='add_domain', error='no SOA or NS records found for domain %s' % bad_domain), + ), + dict( + desc='Try to delete our domain', + command=('realmdomains_mod', [], {'del_domain': our_domain}), + expected=errors.ValidationError( + name='del_domain', error='cannot delete domain of IPA server'), + ), + dict( + desc='Try to delete domain which is not in list', + command=('realmdomains_mod', [], {'del_domain': new_domain_1}), + expected=errors.AttrValueNotFound( + attr='associateddomain', value=new_domain_1), + ), + dict( + desc='Add an invalid domain "%s" with --force option' % bad_domain, + command=('realmdomains_mod', [], {'add_domain': bad_domain, 'force': True}), + expected=dict( + value=u'', + summary=None, + result=dict( + associateddomain=[our_domain, new_domain_2, bad_domain], + ), + ), + ), + ] diff --git a/ipatests/test_xmlrpc/test_replace.py b/ipatests/test_xmlrpc/test_replace.py new file mode 100644 index 000000000..281714b3e --- /dev/null +++ b/ipatests/test_xmlrpc/test_replace.py @@ -0,0 +1,236 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the modlist replace logic. Some attributes require a MOD_REPLACE +while others are fine using ADD/DELETE. + +Note that member management in other tests also exercises the +gen_modlist code. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +user1=u'tuser1' + + +class test_replace(Declarative): + + cleanup_commands = [ + ('user_del', [user1], {}), + ] + + tests = [ + + dict( + desc='Create %r with 2 e-mail accounts' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + mail=[u'test1@example.com', u'test2@example.com']) + ), + expected=dict( + value=user1, + summary=u'Added user "tuser1"', + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + mail=[u'test1@example.com', u'test2@example.com'], + 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','tuser1'),('cn','users'),('cn','accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Drop one e-mail account, add another to %r' % user1, + command=( + 'user_mod', [user1], dict(mail=[u'test1@example.com', u'test3@example.com']) + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test1@example.com', u'test3@example.com'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Set mail to a new single value %r' % user1, + command=( + 'user_mod', [user1], dict(mail=u'test4@example.com') + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test4@example.com'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Set mail to three new values %r' % user1, + command=( + 'user_mod', [user1], dict(mail=[u'test5@example.com', u'test6@example.com', u'test7@example.com']) + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'test6@example.com', u'test7@example.com', u'test5@example.com'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Remove all mail values %r' % user1, + command=( + 'user_mod', [user1], dict(mail=u'') + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Ensure single-value mods work too, replace initials %r' % user1, + command=( + 'user_mod', [user1], dict(initials=u'ABC') + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + initials=[u'ABC'], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + + dict( + desc='Drop a single-value attribute %r' % user1, + command=( + 'user_mod', [user1], dict(initials=u'') + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "tuser1"', + value=user1, + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_role_plugin.py b/ipatests/test_xmlrpc/test_role_plugin.py new file mode 100644 index 000000000..119bfb1a8 --- /dev/null +++ b/ipatests/test_xmlrpc/test_role_plugin.py @@ -0,0 +1,625 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# John Dennis <jdennis@redhat.com> +# +# Copyright (C) 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/role.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN + +search = u'test-role' + +role1 = u'test-role-1' +role1_dn = DN(('cn',role1),api.env.container_rolegroup, + api.env.basedn) +renamedrole1 = u'test-role' +invalidrole1 = u' whitespace ' + +role2 = u'test-role-2' +role2_dn = DN(('cn',role2),api.env.container_rolegroup, + api.env.basedn) + +group1 = u'testgroup1' +group1_dn = DN(('cn',group1),api.env.container_group, + api.env.basedn) + +privilege1 = u'r,w privilege 1' +privilege1_dn = DN(('cn', privilege1), DN(api.env.container_privilege), + api.env.basedn) + +class test_role(Declarative): + + cleanup_commands = [ + ('role_del', [role1], {}), + ('role_del', [role2], {}), + ('group_del', [group1], {}), + ('privilege_del', [privilege1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % role1, + command=('role_show', [role1], {}), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Try to update non-existent %r' % role1, + command=('role_mod', [role1], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Try to delete non-existent %r' % role1, + command=('role_del', [role1], {}), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Try to rename non-existent %r' % role1, + command=('role_mod', [role1], dict(setattr=u'cn=%s' % renamedrole1)), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Search for non-existent %r' % role1, + command=('role_find', [role1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 roles matched', + result=[], + ), + ), + + + dict( + desc='Create invalid %r' % invalidrole1, + command=('role_add', [invalidrole1], + dict(description=u'role desc 1') + ), + expected=errors.ValidationError(name='name', + error=u'Leading and trailing spaces are not allowed'), + ), + + + dict( + desc='Create %r' % role1, + command=('role_add', [role1], + dict(description=u'role desc 1') + ), + expected=dict( + value=role1, + summary=u'Added role "%s"' % role1, + result=dict( + dn=role1_dn, + cn=[role1], + description=[u'role desc 1'], + objectclass=objectclasses.role, + ), + ), + ), + + + dict( + desc='Retrieve %r' % role1, + command=('role_show', [role1], {}), + expected=dict( + value=role1, + summary=None, + result=dict( + dn=role1_dn, + cn=[role1], + description=[u'role desc 1'], + ), + ), + ), + + + dict( + desc='Create %r' % group1, + command=( + 'group_add', [group1], dict(description=u'group desc 1', + nonposix=True,) + ), + expected=dict( + value=group1, + summary=u'Added group "testgroup1"', + result=dict( + dn=group1_dn, + cn=[group1], + description=[u'group desc 1'], + objectclass=objectclasses.group, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Create %r' % privilege1, + command=('privilege_add', [privilege1], + dict(description=u'privilege desc. 1') + ), + expected=dict( + value=privilege1, + summary=u'Added privilege "%s"' % privilege1, + result=dict( + dn=privilege1_dn, + cn=[privilege1], + description=[u'privilege desc. 1'], + objectclass=objectclasses.privilege, + ), + ), + ), + + + dict( + desc='Add privilege %r to role %r' % (privilege1, role1), + command=('role_add_privilege', [role1], + dict(privilege=privilege1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + privilege=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'memberof_privilege': [privilege1], + 'objectclass': objectclasses.role, + } + ), + ), + + + dict( + desc='Add zero privileges to role %r' % role1, + command=('role_add_privilege', [role1], dict(privilege=None) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + privilege=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'memberof_privilege': [privilege1], + 'objectclass': objectclasses.role, + } + ), + ), + + + dict( + desc='Remove zero privileges from role %r' % role1, + command=('role_remove_privilege', [role1], dict(privilege=None) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + privilege=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'memberof_privilege': [privilege1], + 'objectclass': objectclasses.role, + } + ), + ), + + + dict( + desc='Add member %r to %r' % (group1, role1), + command=('role_add_member', [role1], dict(group=group1)), + expected=dict( + completed=1, + failed=dict( + member=dict( + user=[], + group=[], + host=[], + hostgroup=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + } + ), + ), + + + dict( + desc='Retrieve %r to verify member-add' % role1, + command=('role_show', [role1], {}), + expected=dict( + value=role1, + summary=None, + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + ), + ), + + + dict( + desc='Search for %r' % role1, + command=('role_find', [role1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 role matched', + result=[ + { + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + ], + ), + ), + + + dict( + desc='Search for %r' % search, + command=('role_find', [search], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 role matched', + result=[ + { + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + ], + ), + ), + + + dict( + desc='Create %r' % role2, + command=('role_add', [role2], + dict(description=u'role desc 2') + ), + expected=dict( + value=role2, + summary=u'Added role "%s"' % role2, + result=dict( + dn=role2_dn, + cn=[role2], + description=[u'role desc 2'], + objectclass=objectclasses.role, + ), + ), + ), + + + dict( + desc='Search for %r' % role1, + command=('role_find', [role1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 role matched', + result=[ + { + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + ], + ), + ), + + + dict( + desc='Search for %r' % search, + command=('role_find', [search], {}), + expected=dict( + count=2, + truncated=False, + summary=u'2 roles matched', + result=[ + { + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'role desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + { + 'dn': role2_dn, + 'cn': [role2], + 'description': [u'role desc 2'], + }, + ], + ), + ), + + + dict( + desc='Update %r' % role1, + command=( + 'role_mod', [role1], dict(description=u'New desc 1') + ), + expected=dict( + value=role1, + summary=u'Modified role "%s"' % role1, + result=dict( + cn=[role1], + description=[u'New desc 1'], + member_group=[group1], + memberof_privilege=[privilege1], + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % role1, + command=('role_show', [role1], {}), + expected=dict( + value=role1, + summary=None, + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'New desc 1'], + 'member_group': [group1], + 'memberof_privilege': [privilege1], + }, + ), + ), + + + dict( + desc='Remove member %r from %r' % (group1, role1), + command=('role_remove_member', [role1], dict(group=group1)), + expected=dict( + completed=1, + failed=dict( + member=dict( + user=[], + group=[], + host=[], + hostgroup=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'New desc 1'], + 'memberof_privilege': [privilege1], + }, + ), + ), + + + dict( + desc='Retrieve %r to verify member-del' % role1, + command=('role_show', [role1], {}), + expected=dict( + value=role1, + summary=None, + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'New desc 1'], + 'memberof_privilege': [privilege1], + }, + ), + ), + + + dict( + desc='Delete %r' % group1, + command=('group_del', [group1], {}), + expected=dict( + result=dict(failed=u''), + value=group1, + summary=u'Deleted group "testgroup1"', + ) + ), + + + dict( + desc='Rename %r' % role1, + command=('role_mod', [role1], dict(setattr=u'cn=%s' % renamedrole1)), + expected=dict( + value=role1, + result=dict( + cn=[renamedrole1], + description=[u'New desc 1'], + memberof_privilege=[privilege1], + ), + summary=u'Modified role "%s"' % role1 + ) + ), + + + dict( + desc='Rename %r back' % renamedrole1, + command=('role_mod', [renamedrole1], dict(setattr=u'cn=%s' % role1)), + expected=dict( + value=renamedrole1, + result=dict( + cn=[role1], + description=[u'New desc 1'], + memberof_privilege=[privilege1], + ), + summary=u'Modified role "%s"' % renamedrole1 + ) + ), + + + dict( + desc='Remove privilege %r from role %r' % (privilege1, role1), + command=('role_remove_privilege', [role1], + dict(privilege=privilege1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + privilege=[], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'New desc 1'], + 'objectclass': objectclasses.role, + } + ), + ), + + + dict( + desc='Remove privilege %r from role %r again' % (privilege1, role1), + command=('role_remove_privilege', [role1], + dict(privilege=privilege1) + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + privilege=[(u'%s' % privilege1, u'This entry is not a member'),], + ), + ), + result={ + 'dn': role1_dn, + 'cn': [role1], + 'description': [u'New desc 1'], + 'objectclass': objectclasses.role, + } + ), + ), + + + + dict( + desc='Delete %r' % role1, + command=('role_del', [role1], {}), + expected=dict( + result=dict(failed=u''), + value=role1, + summary=u'Deleted role "%s"' % role1, + ) + ), + + + dict( + desc='Try to delete non-existent %r' % role1, + command=('role_del', [role1], {}), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Try to show non-existent %r' % role1, + command=('role_show', [role1], {}), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Try to update non-existent %r' % role1, + command=('role_mod', [role1], dict(description=u'Foo')), + expected=errors.NotFound(reason=u'%s: role not found' % role1), + ), + + + dict( + desc='Search for %r' % search, + command=('role_find', [search], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 role matched', + result=[ + { + 'dn': role2_dn, + 'cn': [role2], + 'description': [u'role desc 2'], + }, + ], + ), + ), + + + dict( + desc='Delete %r' % role2, + command=('role_del', [role2], {}), + expected=dict( + result=dict(failed=u''), + value=role2, + summary=u'Deleted role "%s"' % role2, + ) + ), + + + dict( + desc='Search for %r' % search, + command=('role_find', [search], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 roles matched', + result=[], + ), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py new file mode 100644 index 000000000..c78edbc22 --- /dev/null +++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py @@ -0,0 +1,290 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/selfservice.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid + +selfservice1 = u'testself' +invalid_selfservice1 = u'bad+name' + +class test_selfservice(Declarative): + + cleanup_commands = [ + ('selfservice_del', [selfservice1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % selfservice1, + command=('selfservice_show', [selfservice1], {}), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % selfservice1), + ), + + + dict( + desc='Try to update non-existent %r' % selfservice1, + command=('selfservice_mod', [selfservice1], + dict(permissions=u'write')), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % selfservice1), + ), + + + dict( + desc='Try to delete non-existent %r' % selfservice1, + command=('selfservice_del', [selfservice1], {}), + expected=errors.NotFound( + reason=u'ACI with name "%s" not found' % selfservice1), + ), + + + dict( + desc='Search for non-existent %r' % selfservice1, + command=('selfservice_find', [selfservice1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 selfservices matched', + result=[], + ), + ), + + + # Note that we add postalCode but expect postalcode. This tests + # the attrs normalizer. + dict( + desc='Create %r' % selfservice1, + command=( + 'selfservice_add', [selfservice1], dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=u'write', + ) + ), + expected=dict( + value=selfservice1, + summary=u'Added selfservice "%s"' % selfservice1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=[u'write'], + selfaci=True, + aciname=selfservice1, + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % selfservice1, + command=( + 'selfservice_add', [selfservice1], dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=u'write', + ), + ), + expected=errors.DuplicateEntry(), + ), + + + dict( + desc='Retrieve %r' % selfservice1, + command=('selfservice_show', [selfservice1], {}), + expected=dict( + value=selfservice1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'selfaci': True, + 'aciname': selfservice1, + }, + ), + ), + + + dict( + desc='Retrieve %r with --raw' % selfservice1, + command=('selfservice_show', [selfservice1], {'raw':True}), + expected=dict( + value=selfservice1, + summary=None, + result={ + 'aci': u'(targetattr = "street || c || l || st || postalcode")(version 3.0;acl "selfservice:testself";allow (write) userdn = "ldap:///self";)', + }, + ), + ), + + + dict( + desc='Search for %r' % selfservice1, + command=('selfservice_find', [selfservice1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 selfservice matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'selfaci': True, + 'aciname': selfservice1, + }, + ], + ), + ), + + dict( + desc='Search for %r with --pkey-only' % selfservice1, + command=('selfservice_find', [selfservice1], {'pkey_only' : True}), + expected=dict( + count=1, + truncated=False, + summary=u'1 selfservice matched', + result=[ + { + 'aciname': selfservice1, + }, + ], + ), + ), + + + dict( + desc='Search for %r with empty attrs and permissions' % selfservice1, + command=('selfservice_find', [selfservice1], {'attrs' : None, 'permissions' : None}), + expected=dict( + count=1, + truncated=False, + summary=u'1 selfservice matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'write'], + 'selfaci': True, + 'aciname': selfservice1, + }, + ], + ), + ), + + + dict( + desc='Search for %r with --raw' % selfservice1, + command=('selfservice_find', [selfservice1], {'raw':True}), + expected=dict( + count=1, + truncated=False, + summary=u'1 selfservice matched', + result=[ + { + 'aci': u'(targetattr = "street || c || l || st || postalcode")(version 3.0;acl "selfservice:testself";allow (write) userdn = "ldap:///self";)' + }, + ], + ), + ), + + + dict( + desc='Update %r' % selfservice1, + command=( + 'selfservice_mod', [selfservice1], dict(permissions=u'read') + ), + expected=dict( + value=selfservice1, + summary=u'Modified selfservice "%s"' % selfservice1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=[u'read'], + selfaci=True, + aciname=selfservice1, + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % selfservice1, + command=('selfservice_show', [selfservice1], {}), + expected=dict( + value=selfservice1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'read'], + 'selfaci': True, + 'aciname': selfservice1, + }, + ), + ), + + + dict( + desc='Try to update %r with empty permissions' % selfservice1, + command=( + 'selfservice_mod', [selfservice1], dict(permissions=None) + ), + expected=errors.RequirementError(name='permissions'), + ), + + + dict( + desc='Retrieve %r to verify invalid update' % selfservice1, + command=('selfservice_show', [selfservice1], {}), + expected=dict( + value=selfservice1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalcode'], + 'permissions': [u'read'], + 'selfaci': True, + 'aciname': selfservice1, + }, + ), + ), + + + dict( + desc='Delete %r' % selfservice1, + command=('selfservice_del', [selfservice1], {}), + expected=dict( + result=True, + value=selfservice1, + summary=u'Deleted selfservice "%s"' % selfservice1, + ) + ), + + dict( + desc='Create invalid %r' % invalid_selfservice1, + command=( + 'selfservice_add', [invalid_selfservice1], dict( + attrs=[u'street', u'c', u'l', u'st', u'postalcode'], + permissions=u'write', + ) + ), + expected=errors.ValidationError(name='name', + error='May only contain letters, numbers, -, _, and space'), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py b/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py new file mode 100644 index 000000000..5bfe5475c --- /dev/null +++ b/ipatests/test_xmlrpc/test_selinuxusermap_plugin.py @@ -0,0 +1,934 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2011 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/selinuxusermap.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid +from ipapython.dn import DN +from ipatests.util import Fuzzy + +rule1 = u'selinuxrule1' +selinuxuser1 = u'guest_u:s0' +selinuxuser2 = u'xguest_u:s0' + +user1 = u'tuser1' +group1 = u'testgroup1' +host1 = u'testhost1.%s' % api.env.domain +hostdn1 = DN(('fqdn', host1), ('cn', 'computers'), ('cn', 'accounts'), + api.env.basedn) +hbacrule1 = u'testhbacrule1' +hbacrule2 = u'testhbacrule12' + +# Note (?i) at the beginning of the regexp is the ingnore case flag +fuzzy_selinuxusermapdn = Fuzzy( + '(?i)ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}' + '-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' + % (api.env.container_selinux, api.env.basedn) +) +fuzzy_hbacruledn = Fuzzy( + '(?i)ipauniqueid=[0-9a-f]{8}-[0-9a-f]{4}' + '-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12},%s,%s' + % (api.env.container_hbac, api.env.basedn) +) + +allow_all_rule_dn = api.Command['hbacrule_show'](u'allow_all')['result']['dn'] + + +class test_selinuxusermap(Declarative): + cleanup_commands = [ + ('selinuxusermap_del', [rule1], {}), + ('group_del', [group1], {}), + ('user_del', [user1], {}), + ('host_del', [host1], {}), + ('hbacrule_del', [hbacrule1], {}), + ('hbacrule_del', [hbacrule2], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % rule1, + command=('selinuxusermap_show', [rule1], {}), + expected=errors.NotFound( + reason=u'%s: SELinux User Map rule not found' % rule1), + ), + + + dict( + desc='Try to update non-existent %r' % rule1, + command=('selinuxusermap_mod', [rule1], dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: SELinux User Map rule not found' % rule1), + ), + + + dict( + desc='Try to delete non-existent %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=errors.NotFound( + reason=u'%s: SELinux User Map rule not found' % rule1), + ), + + + dict( + desc='Create rule %r' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1) + ), + expected=dict( + value=rule1, + summary=u'Added SELinux User Map "%s"' % rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + objectclass=objectclasses.selinuxusermap, + ipauniqueid=[fuzzy_uuid], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1) + ), + expected=errors.DuplicateEntry(message=u'SELinux User Map rule ' + + u'with name "%s" already exists' % rule1), + ), + + + dict( + desc='Retrieve rule %r' % rule1, + command=('selinuxusermap_show', [rule1], {}), + expected=dict( + value=rule1, + summary=None, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ), + ), + + + dict( + desc='Update rule %r' % rule1, + command=( + 'selinuxusermap_mod', [rule1], + dict(ipaselinuxuser=selinuxuser2) + ), + expected=dict( + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + ), + summary=u'Modified SELinux User Map "%s"' % rule1, + value=rule1, + ), + ), + + + dict( + desc='Retrieve %r to verify update' % rule1, + command=('selinuxusermap_show', [rule1], {}), + expected=dict( + value=rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + summary=None, + ), + ), + + + dict( + desc='Search for rule %r' % rule1, + command=('selinuxusermap_find', [], dict(cn=rule1)), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ], + summary=u'1 SELinux User Map matched', + ), + ), + + + ############### + # Create additional entries needed for testing + dict( + desc='Create %r' % user1, + command=( + 'user_add', [], dict(givenname=u'Test', sn=u'User1') + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/home/%s' % user1], + krbprincipalname=[u'%s@%s' % (user1, api.env.realm)], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + 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'], + dn=DN(('uid', user1), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), + has_keytab=False, + has_password=False, + ), + ), + ), + + dict( + desc='Create group %r' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc 1') + ), + expected=dict( + value=group1, + summary=u'Added group "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc 1'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn', group1), ('cn', 'groups'), ('cn', 'accounts'), + api.env.basedn), + ), + ), + ), + + + dict( + desc='Add member %r to %r' % (user1, group1), + command=( + 'group_add_member', [group1], dict(user=user1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': DN(('cn', group1), ('cn', 'groups'), + ('cn', 'accounts'), api.env.basedn), + 'member_user': (user1,), + 'gidnumber': [fuzzy_digits], + 'cn': [group1], + 'description': [u'Test desc 1'], + }, + ), + ), + + + dict( + desc='Create host %r' % host1, + command=('host_add', [host1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=host1, + summary=u'Added host "%s"' % host1, + result=dict( + dn=hostdn1, + fqdn=[host1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (host1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[host1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create HBAC rule %r' % hbacrule1, + command=( + 'hbacrule_add', [hbacrule1], {} + ), + expected=dict( + value=hbacrule1, + summary=u'Added HBAC rule "%s"' % hbacrule1, + result=dict( + cn=[hbacrule1], + objectclass=objectclasses.hbacrule, + ipauniqueid=[fuzzy_uuid], + accessruletype=[u'allow'], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_hbacruledn, + ), + ), + ), + + + dict( + desc='Create HBAC rule %r' % hbacrule2, + command=( + 'hbacrule_add', [hbacrule2], {} + ), + expected=dict( + value=hbacrule2, + summary=u'Added HBAC rule "%s"' % hbacrule2, + result=dict( + cn=[hbacrule2], + objectclass=objectclasses.hbacrule, + ipauniqueid=[fuzzy_uuid], + accessruletype=[u'allow'], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_hbacruledn, + ), + ), + ), + + + ############### + # Fill out rule with members and/or pointers to HBAC rules + dict( + desc='Add user to %r' % rule1, + command=('selinuxusermap_add_user', [rule1], dict(user=user1)), + expected=dict( + failed=dict(memberuser=dict(group=[], user=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + memberuser_user=[user1], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Add non-existent user to %r' % rule1, + command=('selinuxusermap_add_user', [rule1], + dict(user=u'notfound')), + expected=dict( + failed=dict( + memberuser=dict(group=[], + user=[(u'notfound', u'no such entry')]) + ), + completed=0, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + memberuser_user=[user1], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Remove user from %r' % rule1, + command=('selinuxusermap_remove_user', [rule1], dict(user=user1)), + expected=dict( + failed=dict(memberuser=dict(group=[], user=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Remove non-existent user to %r' % rule1, + command=('selinuxusermap_remove_user', [rule1], + dict(user=u'notfound')), + expected=dict( + failed=dict( + memberuser=dict(group=[], + user=[(u'notfound', u'This entry is not a member')] + ) + ), + completed=0, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Add group to %r' % rule1, + command=('selinuxusermap_add_user', [rule1], dict(group=group1)), + expected=dict( + failed=dict(memberuser=dict(group=[], user=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + memberuser_group=[group1], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Add host to %r' % rule1, + command=('selinuxusermap_add_host', [rule1], dict(host=host1)), + expected=dict( + failed=dict(memberhost=dict(hostgroup=[], host=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + memberhost_host=[host1], + memberuser_group=[group1], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + ############### + # Test enabling and disabling + dict( + desc='Disable %r' % rule1, + command=('selinuxusermap_disable', [rule1], {}), + expected=dict( + result=True, + value=rule1, + summary=u'Disabled SELinux User Map "%s"' % rule1, + ) + ), + + + dict( + desc='Disable %r again' % rule1, + command=('selinuxusermap_disable', [rule1], {}), + expected=errors.AlreadyInactive(), + ), + + + dict( + desc='Enable %r' % rule1, + command=('selinuxusermap_enable', [rule1], {}), + expected=dict( + result=True, + value=rule1, + summary=u'Enabled SELinux User Map "%s"' % rule1, + ) + ), + + + dict( + desc='Re-enable %r again' % rule1, + command=('selinuxusermap_enable', [rule1], {}), + expected=errors.AlreadyActive(), + ), + + + # Point to an HBAC Rule + dict( + desc='Add an HBAC rule to %r that has other members' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(seealso=hbacrule1) + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + + dict( + desc='Remove host from %r' % rule1, + command=('selinuxusermap_remove_host', [rule1], dict(host=host1)), + expected=dict( + failed=dict(memberhost=dict(hostgroup=[], host=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + memberuser_group=[group1], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Remove group from %r' % rule1, + command=('selinuxusermap_remove_user', [rule1], + dict(group=group1)), + expected=dict( + failed=dict(memberuser=dict(group=[], user=[])), + completed=1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ) + ), + + + dict( + desc='Add non-existent HBAC rule to %r' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(seealso=u'notfound') + ), + expected=errors.NotFound( + reason=u'HBAC rule notfound not found'), + ), + + + dict( + desc='Add an HBAC rule to %r' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(seealso=hbacrule1) + ), + expected=dict( + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser2], + ipaenabledflag=[u'TRUE'], + seealso=hbacrule1, + ), + summary=u'Modified SELinux User Map "%s"' % rule1, + value=rule1, + ), + ), + + + dict( + desc='Add user to %r that has HBAC' % rule1, + command=('selinuxusermap_add_user', [rule1], dict(user=user1)), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + + dict( + desc='Add host to %r that has HBAC' % rule1, + command=('selinuxusermap_add_host', [rule1], dict(host=host1)), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + + dict( + desc='Try to delete HBAC rule pointed to by %r' % rule1, + command=('hbacrule_del', [hbacrule1], {}), + expected=errors.DependentEntry(key=hbacrule1, + label=u'SELinux User Map', dependent=rule1) + ), + + + # This tests selinuxusermap-find --hbacrule=<foo> returns an + # exact match + dict( + desc='Try to delete similarly named HBAC rule %r' % hbacrule2, + command=('hbacrule_del', [hbacrule2], {}), + expected=dict( + result=dict(failed=u''), + value=hbacrule2, + summary=u'Deleted HBAC rule "%s"' % hbacrule2, + ) + ), + + + # Test clean up + dict( + desc='Delete %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=dict( + result=dict(failed=u''), + value=rule1, + summary=u'Deleted SELinux User Map "%s"' % rule1, + ) + ), + + + dict( + desc='Try to delete non-existent %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=errors.NotFound( + reason=u'%s: SELinux User Map rule not found' % rule1), + ), + + + # Some negative tests + dict( + desc='Create rule with unknown user %r' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=u'notfound:s0:c0') + ), + expected=errors.NotFound(reason=u'SELinux user notfound:s0:c0 ' + + u'not found in ordering list (in config)'), + ), + + + dict( + desc='Create rule with invalid user bad+user', + command=( + 'selinuxusermap_add', [rule1], dict(ipaselinuxuser=u'bad+user') + ), + expected=errors.ValidationError(name='selinuxuser', + error=u'Invalid SELinux user name, only a-Z and _ are allowed' + ), + ), + + + dict( + desc='Create rule with invalid MCS xguest_u:s999', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=u'xguest_u:s999') + ), + expected=errors.ValidationError(name='selinuxuser', + error=u'Invalid MLS value, must match s[0-15](-s[0-15])'), + ), + + + dict( + desc='Create rule with invalid MLS xguest_u:s0:p88', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=u'xguest_u:s0:p88') + ), + expected=errors.ValidationError(name='selinuxuser', + error=u'Invalid MCS value, must match c[0-1023].c[0-1023] ' + + u'and/or c[0-1023]-c[0-c0123]'), + ), + + + dict( + desc='Create rule with invalid MLS xguest_u:s0:c0.c1028', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=u'xguest_u:s0-s0:c0.c1028') + ), + expected=errors.ValidationError(name='selinuxuser', + error=u'Invalid MCS value, must match c[0-1023].c[0-1023] ' + + u'and/or c[0-1023]-c[0-c0123]'), + ), + + + dict( + desc='Create rule with invalid user via setattr', + command=( + 'selinuxusermap_mod', [rule1], + dict(setattr=u'ipaselinuxuser=deny') + ), + expected=errors.ValidationError(name='ipaselinuxuser', + error=u'Invalid MLS value, must match s[0-15](-s[0-15])'), + ), + + dict( + desc='Create rule with both --hbacrule and --usercat set', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, + seealso=hbacrule1, + usercategory=u'all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Create rule with both --hbacrule and --hostcat set', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, + seealso=hbacrule1, + hostcategory=u'all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Create rule with both --hbacrule ' + 'and --usercat set via setattr', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, + seealso=hbacrule1, + setattr=u'usercategory=all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Create rule with both --hbacrule ' + 'and --hostcat set via setattr', + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, + seealso=hbacrule1, + setattr=u'hostcategory=all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Create rule %r with --hbacrule' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, seealso=hbacrule1) + ), + expected=dict( + value=rule1, + summary=u'Added SELinux User Map "%s"' % rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + objectclass=objectclasses.selinuxusermap, + ipauniqueid=[fuzzy_uuid], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + seealso=hbacrule1 + ), + ), + ), + + dict( + desc='Add an --usercat to %r that has HBAC set' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(usercategory=u'all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Add an --hostcat to %r that has HBAC set' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(hostcategory=u'all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Add an usercat via setattr to %r that has HBAC set' % rule1, + command=( + 'selinuxusermap_mod', [rule1], + dict(setattr=u'usercategory=all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Add an hostcat via setattr to %r that has HBAC set' % rule1, + command=( + 'selinuxusermap_mod', [rule1], + dict(setattr=u'hostcategory=all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Delete %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=dict( + result=dict(failed=u''), + value=rule1, + summary=u'Deleted SELinux User Map "%s"' % rule1, + ) + ), + + dict( + desc='Create rule %r with usercat and hostcat set' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1, + usercategory=u'all', + hostcategory=u'all') + ), + expected=dict( + value=rule1, + summary=u'Added SELinux User Map "%s"' % rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + objectclass=objectclasses.selinuxusermap, + ipauniqueid=[fuzzy_uuid], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + usercategory=[u'all'], + hostcategory=[u'all'] + ), + ), + ), + + dict( + desc='Add HBAC rule to %r that has usercat and hostcat' % rule1, + command=( + 'selinuxusermap_mod', [rule1], dict(seealso=hbacrule1) + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Delete %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=dict( + result=dict(failed=u''), + value=rule1, + summary=u'Deleted SELinux User Map "%s"' % rule1, + ) + ), + + dict( + desc='Create rule %r' % rule1, + command=( + 'selinuxusermap_add', [rule1], + dict(ipaselinuxuser=selinuxuser1) + ), + expected=dict( + value=rule1, + summary=u'Added SELinux User Map "%s"' % rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + objectclass=objectclasses.selinuxusermap, + ipauniqueid=[fuzzy_uuid], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + ), + ), + ), + + dict( + desc='Add HBAC rule, hostcat and usercat to %r' % rule1, + command=( + 'selinuxusermap_mod', [rule1], + dict(seealso=hbacrule1, + usercategory=u'all', + hostcategory=u'all') + ), + expected=errors.MutuallyExclusiveError( + reason=u'HBAC rule and local members cannot both be set'), + ), + + dict( + desc='Delete %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=dict( + result=dict(failed=u''), + value=rule1, + summary=u'Deleted SELinux User Map "%s"' % rule1, + ) + ), + + dict( + desc='Create rule %r with ' + '--setattr=seealso=<allow_all rule DN>' % rule1, + command=( + 'selinuxusermap_add', + [rule1], + dict(ipaselinuxuser=selinuxuser1, + setattr=u'seealso=%s' % allow_all_rule_dn) + ), + expected=dict( + value=rule1, + summary=u'Added SELinux User Map "%s"' % rule1, + result=dict( + cn=[rule1], + ipaselinuxuser=[selinuxuser1], + objectclass=objectclasses.selinuxusermap, + ipauniqueid=[fuzzy_uuid], + ipaenabledflag=[u'TRUE'], + dn=fuzzy_selinuxusermapdn, + seealso=u'allow_all', + ), + ), + ), + + dict( + desc='Delete %r' % rule1, + command=('selinuxusermap_del', [rule1], {}), + expected=dict( + result=dict(failed=u''), + value=rule1, + summary=u'Deleted SELinux User Map "%s"' % rule1, + ) + ), + ] diff --git a/ipatests/test_xmlrpc/test_service_plugin.py b/ipatests/test_xmlrpc/test_service_plugin.py new file mode 100644 index 000000000..f51954eb3 --- /dev/null +++ b/ipatests/test_xmlrpc/test_service_plugin.py @@ -0,0 +1,632 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/service.py` module. +""" + +from ipalib import api, errors, x509 +from ipatests.test_xmlrpc.xmlrpc_test import Declarative, fuzzy_uuid, fuzzy_hash +from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_digits, fuzzy_date, fuzzy_issuer +from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_hex +from ipatests.test_xmlrpc import objectclasses +import base64 +from ipapython.dn import DN + +fqdn1 = u'testhost1.%s' % api.env.domain +fqdn2 = u'testhost2.%s' % api.env.domain +fqdn3 = u'TestHost3.%s' % api.env.domain +service1 = u'HTTP/%s@%s' % (fqdn1, api.env.realm) +hostprincipal1 = u'host/%s@%s' % (fqdn1, api.env.realm) +service1dn = DN(('krbprincipalname',service1),('cn','services'),('cn','accounts'),api.env.basedn) +host1dn = DN(('fqdn',fqdn1),('cn','computers'),('cn','accounts'),api.env.basedn) +host2dn = DN(('fqdn',fqdn2),('cn','computers'),('cn','accounts'),api.env.basedn) +host3dn = DN(('fqdn',fqdn3),('cn','computers'),('cn','accounts'),api.env.basedn) + +fd = open('ipatests/test_xmlrpc/service.crt', 'r') +servercert = fd.readlines() +servercert = ''.join(servercert) +servercert = x509.strip_header(servercert) +fd.close() + +badservercert = 'MIICbzCCAdigAwIBAgICA/4wDQYJKoZIhvcNAQEFBQAwKTEnMCUGA1UEAxMeSVBBIFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTEwMDgwOTE1MDIyN1oXDTIwMDgwOTE1MDIyN1owKTEMMAoGA1UEChMDSVBBMRkwFwYDVQQDExBwdW1hLmdyZXlvYWsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwYbfEOQPgGenPn9vt1JFKvWm/Je3y2tawGWA3LXDuqfFJyYtZ8ib3TcBUOnLk9WK5g2qCwHaNlei7bj8ggIfr5hegAVe10cun+wYErjnYo7hsHYd+57VZezeipWrXu+7NoNd4+c4A5lk4A/xJay9j3bYx2oOM8BEox4xWYoWge1ljPrc5JK46f0X7AGW4F2VhnKPnf8rwSuzI1U8VGjutyM9TWNy3m9KMWeScjyG/ggIpOjUDMV7HkJL0Di61lznR9jXubpiEC7gWGbTp84eGl/Nn9bgK1AwHfJ2lHwfoY4uiL7ge1gyP6EvuUlHoBzdb7pekiX28iePjW3iEG9IawIDAQABoyIwIDARBglghkgBhvhCAQEEBAMCBkAwCwYDVR0PBAQDAgUgMA0GCSqGSIb3DQEBBQUAA4GBACRESLemRV9BPxfEgbALuxH5oE8jQm8WZ3pm2pALbpDlAd9wQc3yVf6RtkfVthyDnM18bg7IhxKpd77/p3H8eCnS8w5MLVRda6ktUC6tGhFTS4QKAf0WyDGTcIgkXbeDw0OPAoNHivoXbIXIIRxlw/XgaSaMzJQDBG8iROsN4kCv' + + +class test_service(Declarative): + + cleanup_commands = [ + ('host_del', [fqdn1], {}), + ('host_del', [fqdn2], {}), + ('host_del', [fqdn3], {}), + ('service_del', [service1], {}), + ] + + tests = [ + dict( + desc='Try to retrieve non-existent %r' % service1, + command=('service_show', [service1], {}), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Try to update non-existent %r' % service1, + command=('service_mod', [service1], dict(usercertificate=servercert)), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Try to delete non-existent %r' % service1, + command=('service_del', [service1], {}), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Create %r' % fqdn1, + command=('host_add', [fqdn1], + dict( + description=u'Test host 1', + l=u'Undisclosed location 1', + force=True, + ), + ), + expected=dict( + value=fqdn1, + summary=u'Added host "%s"' % fqdn1, + result=dict( + dn=host1dn, + fqdn=[fqdn1], + description=[u'Test host 1'], + l=[u'Undisclosed location 1'], + krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn1], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % fqdn2, + command=('host_add', [fqdn2], + dict( + description=u'Test host 2', + l=u'Undisclosed location 2', + force=True, + ), + ), + expected=dict( + value=fqdn2, + summary=u'Added host "%s"' % fqdn2, + result=dict( + dn=host2dn, + fqdn=[fqdn2], + description=[u'Test host 2'], + l=[u'Undisclosed location 2'], + krbprincipalname=[u'host/%s@%s' % (fqdn2, api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn2], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % fqdn3, + command=('host_add', [fqdn3], + dict( + description=u'Test host 3', + l=u'Undisclosed location 3', + force=True, + ), + ), + expected=dict( + value=fqdn3.lower(), + summary=u'Added host "%s"' % fqdn3.lower(), + result=dict( + dn=host3dn, + fqdn=[fqdn3.lower()], + description=[u'Test host 3'], + l=[u'Undisclosed location 3'], + krbprincipalname=[u'host/%s@%s' % (fqdn3.lower(), api.env.realm)], + objectclass=objectclasses.host, + ipauniqueid=[fuzzy_uuid], + managedby_host=[u'%s' % fqdn3.lower()], + has_keytab=False, + has_password=False, + ), + ), + ), + + + dict( + desc='Create %r' % service1, + command=('service_add', [service1], + dict( + force=True, + ), + ), + expected=dict( + value=service1, + summary=u'Added service "%s"' % service1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + objectclass=objectclasses.service, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % service1, + command=('service_add', [service1], + dict( + force=True, + ), + ), + expected=errors.DuplicateEntry( + message=u'service with name "%s" already exists' % service1), + ), + + + dict( + desc='Retrieve %r' % service1, + command=('service_show', [service1], {}), + expected=dict( + value=service1, + summary=None, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + has_keytab=False, + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Retrieve %r with all=True' % service1, + command=('service_show', [service1], dict(all=True)), + expected=dict( + value=service1, + summary=None, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + ipakrbprincipalalias=[service1], + objectclass=objectclasses.service, + ipauniqueid=[fuzzy_uuid], + managedby_host=[fqdn1], + has_keytab=False, + ipakrbrequirespreauth=True, + ipakrbokasdelegate=False, + ), + ), + ), + + + dict( + desc='Search for %r' % service1, + command=('service_find', [service1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 service matched', + result=[ + dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + has_keytab=False, + ), + ], + ), + ), + + + dict( + desc='Search for %r with all=True' % service1, + command=('service_find', [service1], dict(all=True)), + expected=dict( + count=1, + truncated=False, + summary=u'1 service matched', + result=[ + dict( + dn=service1dn, + krbprincipalname=[service1], + ipakrbprincipalalias=[service1], + objectclass=objectclasses.service, + ipauniqueid=[fuzzy_uuid], + has_keytab=False, + managedby_host=[fqdn1], + ipakrbrequirespreauth=True, + ipakrbokasdelegate=False, + ), + ], + ), + ), + + + dict( + desc='Add non-existent host to %r' % service1, + command=('service_add_host', [service1], dict(host=u'notfound')), + expected=dict( + failed=dict(managedby=dict(host=[(u'notfound', u'no such entry')])), + completed=0, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Remove non-existent host from %r' % service1, + command=('service_remove_host', [service1], dict(host=u'notfound')), + expected=dict( + failed=dict(managedby=dict(host=[(u'notfound', u'This entry is not a member')])), + completed=0, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Add host to %r' % service1, + command=('service_add_host', [service1], dict(host=fqdn2)), + expected=dict( + failed=dict(managedby=dict(host=[])), + completed=1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1, fqdn2], + ), + ), + ), + + + dict( + desc='Remove host from %r' % service1, + command=('service_remove_host', [service1], dict(host=fqdn2)), + expected=dict( + failed=dict(managedby=dict(host=[])), + completed=1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Add mixed-case host to %r' % service1, + command=('service_add_host', [service1], dict(host=fqdn3)), + expected=dict( + failed=dict(managedby=dict(host=[])), + completed=1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1, fqdn3.lower()], + ), + ), + ), + + + dict( + desc='Remove mixed-case host from %r' % service1, + command=('service_remove_host', [service1], dict(host=fqdn3)), + expected=dict( + failed=dict(managedby=dict(host=[])), + completed=1, + result=dict( + dn=service1dn, + krbprincipalname=[service1], + managedby_host=[fqdn1], + ), + ), + ), + + + dict( + desc='Update %r with a bad certificate' % service1, + command=('service_mod', [service1], dict(usercertificate=badservercert)), + expected=errors.CertificateOperationError( + error=u'Issuer "CN=IPA Test Certificate Authority" does not ' + + u'match the expected issuer'), + ), + + + dict( + desc='Update %r' % service1, + command=('service_mod', [service1], dict(usercertificate=servercert)), + expected=dict( + value=service1, + summary=u'Modified service "%s"' % service1, + result=dict( + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + managedby_host=[fqdn1], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + ), + ), + ), + + + dict( + desc='Try to update %r with invalid ipakrbauthz data ' + 'combination' % service1, + command=('service_mod', [service1], + dict(ipakrbauthzdata=[u'MS-PAC', u'NONE'])), + expected=errors.ValidationError(name='ipakrbauthzdata', + error=u'NONE value cannot be combined with other PAC types') + ), + + + dict( + desc='Update %r with valid ipakrbauthz data ' + 'combination' % service1, + command=('service_mod', [service1], + dict(ipakrbauthzdata=[u'MS-PAC'])), + expected=dict( + value=service1, + summary=u'Modified service "%s"' % service1, + result=dict( + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + managedby_host=[fqdn1], + ipakrbauthzdata=[u'MS-PAC'], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % service1, + command=('service_show', [service1], {}), + expected=dict( + value=service1, + summary=None, + result=dict( + dn=service1dn, + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + has_keytab=False, + managedby_host=[fqdn1], + ipakrbauthzdata=[u'MS-PAC'], + # These values come from the servercert that is in this + # test case. + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + ), + ), + ), + + + dict( + desc='Enable %r OK_AS_DELEGATE Kerberos ticket flag' % service1, + command=('service_mod', [service1], dict(ipakrbokasdelegate=True)), + expected=dict( + value=service1, + summary=u'Modified service "%s"' % service1, + result=dict( + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + managedby_host=[fqdn1], + ipakrbauthzdata=[u'MS-PAC'], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + krbticketflags=[u'1048704'], + ipakrbokasdelegate=True, + ), + ), + ), + + + dict( + desc='Update %r Kerberos ticket flags with setattr' % service1, + command=('service_mod', [service1], + dict(setattr=[u'krbTicketFlags=1048577'])), + expected=dict( + value=service1, + summary=u'Modified service "%s"' % service1, + result=dict( + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + managedby_host=[fqdn1], + ipakrbauthzdata=[u'MS-PAC'], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + krbticketflags=[u'1048577'], + ), + ), + ), + + + dict( + desc='Disable %r OK_AS_DELEGATE Kerberos ticket flag' % service1, + command=('service_mod', [service1], dict(ipakrbokasdelegate=False)), + expected=dict( + value=service1, + summary=u'Modified service "%s"' % service1, + result=dict( + usercertificate=[base64.b64decode(servercert)], + krbprincipalname=[service1], + managedby_host=[fqdn1], + ipakrbauthzdata=[u'MS-PAC'], + valid_not_before=fuzzy_date, + valid_not_after=fuzzy_date, + subject=DN(('CN',api.env.host),x509.subject_base()), + serial_number=fuzzy_digits, + serial_number_hex=fuzzy_hex, + md5_fingerprint=fuzzy_hash, + sha1_fingerprint=fuzzy_hash, + issuer=fuzzy_issuer, + krbticketflags=[u'1'], + ipakrbokasdelegate=False, + ), + ), + ), + + + dict( + desc='Delete %r' % service1, + command=('service_del', [service1], {}), + expected=dict( + value=service1, + summary=u'Deleted service "%s"' % service1, + result=dict(failed=u''), + ), + ), + + + dict( + desc='Try to retrieve non-existent %r' % service1, + command=('service_show', [service1], {}), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Try to update non-existent %r' % service1, + command=('service_mod', [service1], dict(usercertificate=servercert)), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Try to delete non-existent %r' % service1, + command=('service_del', [service1], {}), + expected=errors.NotFound( + reason=u'%s: service not found' % service1), + ), + + + dict( + desc='Create service with malformed principal "foo"', + command=('service_add', [u'foo'], {}), + expected=errors.MalformedServicePrincipal(reason='missing service') + ), + + + dict( + desc='Create service with bad realm "HTTP/foo@FOO.NET"', + command=('service_add', [u'HTTP/foo@FOO.NET'], {}), + expected=errors.RealmMismatch(), + ), + + + dict( + desc='Create a host service %r' % hostprincipal1, + command=('service_add', [hostprincipal1], {}), + expected=errors.HostService() + ), + + + # These tests will only succeed when running against lite-server.py + # on same box as IPA install. + dict( + desc='Delete the current host (master?) %s HTTP service, should be caught' % api.env.host, + command=('service_del', ['HTTP/%s' % api.env.host], {}), + expected=errors.ValidationError(name='principal', error='This principal is required by the IPA master'), + ), + + + dict( + desc='Delete the current host (master?) %s ldap service, should be caught' % api.env.host, + command=('service_del', ['ldap/%s' % api.env.host], {}), + expected=errors.ValidationError(name='principal', error='This principal is required by the IPA master'), + ), + + + dict( + desc='Disable the current host (master?) %s HTTP service, should be caught' % api.env.host, + command=('service_disable', ['HTTP/%s' % api.env.host], {}), + expected=errors.ValidationError(name='principal', error='This principal is required by the IPA master'), + ), + + + dict( + desc='Disable the current host (master?) %s ldap service, should be caught' % api.env.host, + command=('service_disable', ['ldap/%s' % api.env.host], {}), + expected=errors.ValidationError(name='principal', error='This principal is required by the IPA master'), + ), + + + ] diff --git a/ipatests/test_xmlrpc/test_sudocmd_plugin.py b/ipatests/test_xmlrpc/test_sudocmd_plugin.py new file mode 100644 index 000000000..fe91705c2 --- /dev/null +++ b/ipatests/test_xmlrpc/test_sudocmd_plugin.py @@ -0,0 +1,327 @@ +# Authors: +# Jr Aquino <jr.aquino@citrixonline.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/sudocmd.py` module. +""" + +from ipalib import errors +from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, fuzzy_sudocmddn, + fuzzy_uuid) +from ipatests.test_xmlrpc import objectclasses + +sudocmd1 = u'/usr/bin/sudotestcmd1' +sudocmd1_camelcase = u'/usr/bin/sudoTestCmd1' + +sudorule1 = u'test_sudorule1' + + +class test_sudocmd(Declarative): + + cleanup_commands = [ + ('sudocmd_del', [sudocmd1], {}), + ('sudocmd_del', [sudocmd1_camelcase], {}), + ('sudorule_del', [sudorule1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmd1, + command=('sudocmd_mod', [sudocmd1], dict(description=u'Nope')), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmd1, + command=('sudocmd_del', [sudocmd1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + + dict( + desc='Create %r' % sudocmd1, + command=('sudocmd_add', [sudocmd1], + dict( + description=u'Test sudo command 1', + ), + ), + expected=dict( + value=sudocmd1, + summary=u'Added Sudo Command "%s"' % sudocmd1, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1], + description=[u'Test sudo command 1'], + objectclass=objectclasses.sudocmd, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + dict( + desc='Create %r' % sudocmd1_camelcase, + command=('sudocmd_add', [sudocmd1_camelcase], + dict( + description=u'Test sudo command 2', + ), + ), + expected=dict( + value=sudocmd1_camelcase, + summary=u'Added Sudo Command "%s"' % sudocmd1_camelcase, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1_camelcase], + description=[u'Test sudo command 2'], + objectclass=objectclasses.sudocmd, + ipauniqueid=[fuzzy_uuid], + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % sudocmd1, + command=('sudocmd_add', [sudocmd1], + dict( + description=u'Test sudo command 1', + ), + ), + expected=errors.DuplicateEntry(message=u'sudo command with ' + + u'name "%s" already exists' % sudocmd1), + ), + + dict( + desc='Try to create duplicate %r' % sudocmd1_camelcase, + command=('sudocmd_add', [sudocmd1_camelcase], + dict( + description=u'Test sudo command 2', + ), + ), + expected=errors.DuplicateEntry(message=u'sudo command with ' + + u'name "%s" already exists' % sudocmd1_camelcase), + ), + + + dict( + desc='Retrieve %r' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=dict( + value=sudocmd1, + summary=None, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1], + description=[u'Test sudo command 1'], + ), + ), + ), + + + dict( + desc='Search for %r' % sudocmd1, + command=('sudocmd_find', [sudocmd1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 Sudo Command matched', + result=[ + dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1], + description=[u'Test sudo command 1'], + ), + ], + ), + ), + + dict( + desc='Search for %r' % sudocmd1_camelcase, + command=('sudocmd_find', [sudocmd1_camelcase], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 Sudo Command matched', + result=[ + dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1_camelcase], + description=[u'Test sudo command 2'], + ), + ], + ), + ), + + + dict( + desc='Update %r' % sudocmd1, + command=('sudocmd_mod', [sudocmd1], dict( + description=u'Updated sudo command 1')), + expected=dict( + value=sudocmd1, + summary=u'Modified Sudo Command "%s"' % sudocmd1, + result=dict( + sudocmd=[sudocmd1], + description=[u'Updated sudo command 1'], + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=dict( + value=sudocmd1, + summary=None, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1], + description=[u'Updated sudo command 1'], + ), + ), + ), + + dict( + desc='Create %r' % sudorule1, + command=('sudorule_add', [sudorule1], {}), + expected=lambda e, result: True, + ), + + dict( + desc='Add %r to %r allow list' % (sudocmd1, sudorule1), + command=('sudorule_add_allow_command', [sudorule1], + dict(sudocmd=sudocmd1)), + expected=dict( + completed=1, + failed=dict( + memberallowcmd=dict(sudocmdgroup=(), sudocmd=())), + result=lambda result: True, + ), + ), + + dict( + desc="Test %r can't be deleted when in %r" % (sudocmd1, sudorule1), + command=('sudocmd_del', [sudocmd1], {}), + expected=errors.DependentEntry(key=sudocmd1, label='sudorule', + dependent=sudorule1), + ), + + dict( + desc='Remove %r from %r' % (sudocmd1, sudorule1), + command=('sudorule_remove_allow_command', [sudorule1], + dict(sudocmd=sudocmd1)), + expected=dict( + completed=1, + failed=dict( + memberallowcmd=dict(sudocmdgroup=(), sudocmd=())), + result=lambda result: True, + ), + ), + + dict( + desc='Add %r to %r deny list' % (sudocmd1, sudorule1), + command=('sudorule_add_deny_command', [sudorule1], + dict(sudocmd=sudocmd1)), + expected=dict( + completed=1, + failed=dict( + memberdenycmd=dict(sudocmdgroup=(), sudocmd=())), + result=lambda result: True, + ), + ), + + dict( + desc="Test %r can't be deleted when in %r" % (sudocmd1, sudorule1), + command=('sudocmd_del', [sudocmd1], {}), + expected=errors.DependentEntry(key=sudocmd1, label='sudorule', + dependent=sudorule1), + ), + + dict( + desc='Remove %r from %r' % (sudocmd1, sudorule1), + command=('sudorule_remove_deny_command', [sudorule1], + dict(sudocmd=sudocmd1)), + expected=dict( + completed=1, + failed=dict( + memberdenycmd=dict(sudocmdgroup=(), sudocmd=())), + result=lambda result: True, + ), + ), + + dict( + desc='Delete %r' % sudocmd1, + command=('sudocmd_del', [sudocmd1], {}), + expected=dict( + value=sudocmd1, + summary=u'Deleted Sudo Command "%s"' % sudocmd1, + result=dict(failed=u''), + ), + ), + + + dict( + desc='Try to retrieve non-existent %r' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmd1, + command=('sudocmd_mod', [sudocmd1], dict(description=u'Nope')), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmd1, + command=('sudocmd_del', [sudocmd1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + dict( + desc='Retrieve %r' % sudocmd1_camelcase, + command=('sudocmd_show', [sudocmd1_camelcase], {}), + expected=dict( + value=sudocmd1_camelcase, + summary=None, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1_camelcase], + description=[u'Test sudo command 2'], + ), + ), + ), + ] diff --git a/ipatests/test_xmlrpc/test_sudocmdgroup_plugin.py b/ipatests/test_xmlrpc/test_sudocmdgroup_plugin.py new file mode 100644 index 000000000..397d47683 --- /dev/null +++ b/ipatests/test_xmlrpc/test_sudocmdgroup_plugin.py @@ -0,0 +1,693 @@ +# Authors: +# Jr Aquino <jr.aquino@citrixonline.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/sudocmdgroup.py` module. +""" + +from ipalib import api, errors +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_uuid, fuzzy_sudocmddn +from ipapython.dn import DN + +sudocmdgroup1 = u'testsudocmdgroup1' +sudocmdgroup2 = u'testsudocmdgroup2' +sudocmd1 = u'/usr/bin/sudotestcmd1' +sudocmd1_camelcase = u'/usr/bin/sudoTestCmd1' +sudocmd_plus = u'/bin/ls -l /lost+found/*' + +def create_command(sudocmd): + return dict( + desc='Create %r' % sudocmd, + command=( + 'sudocmd_add', [], dict(sudocmd=sudocmd, + description=u'Test sudo command') + ), + expected=dict( + value=sudocmd, + summary=u'Added Sudo Command "%s"' % sudocmd, + result=dict( + objectclass=objectclasses.sudocmd, + sudocmd=[sudocmd], + ipauniqueid=[fuzzy_uuid], description=[u'Test sudo command'], + dn=fuzzy_sudocmddn, + ), + ), + ) + +class test_sudocmdgroup(Declarative): + cleanup_commands = [ + ('sudocmdgroup_del', [sudocmdgroup1], {}), + ('sudocmdgroup_del', [sudocmdgroup2], {}), + ('sudocmd_del', [sudocmd1], {}), + ('sudocmd_del', [sudocmd1_camelcase], {}), + ('sudocmd_del', [sudocmd_plus], {}), + ] + + tests = [ + + ################ + # create sudo command + dict( + desc='Create %r' % sudocmd1, + command=( + 'sudocmd_add', [], dict(sudocmd=sudocmd1, description=u'Test sudo command 1') + ), + expected=dict( + value=sudocmd1, + summary=u'Added Sudo Command "%s"' % sudocmd1, + result=dict( + objectclass=objectclasses.sudocmd, + sudocmd=[u'/usr/bin/sudotestcmd1'], + ipauniqueid=[fuzzy_uuid], + description=[u'Test sudo command 1'], + dn=fuzzy_sudocmddn, + ), + ), + ), + + dict( + desc='Create %r' % sudocmd1_camelcase, + command=( + 'sudocmd_add', [], dict(sudocmd=sudocmd1_camelcase, description=u'Test sudo command 2') + ), + expected=dict( + value=sudocmd1_camelcase, + summary=u'Added Sudo Command "%s"' % sudocmd1_camelcase, + result=dict( + objectclass=objectclasses.sudocmd, + sudocmd=[u'/usr/bin/sudoTestCmd1'], + ipauniqueid=[fuzzy_uuid], + description=[u'Test sudo command 2'], + dn=fuzzy_sudocmddn, + ), + ), + ), + + dict( + desc='Verify the managed sudo command %r was created' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=dict( + value=sudocmd1, + summary=None, + result=dict( + sudocmd=[sudocmd1], + description=[u'Test sudo command 1'], + dn=fuzzy_sudocmddn, + ), + ), + ), + + + ################ + # create sudo command group1: + dict( + desc='Try to retrieve non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_show', [sudocmdgroup1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_mod', [sudocmdgroup1], + dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_del', [sudocmdgroup1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + dict( + desc='Create %r' % sudocmdgroup1, + command=( + 'sudocmdgroup_add', [sudocmdgroup1], + dict(description=u'Test desc 1') + ), + expected=dict( + value=sudocmdgroup1, + summary=u'Added Sudo Command Group "testsudocmdgroup1"', + result=dict( + cn=[sudocmdgroup1], + description=[u'Test desc 1'], + objectclass=objectclasses.sudocmdgroup, + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % sudocmdgroup1, + command=( + 'sudocmdgroup_add', [sudocmdgroup1], + dict(description=u'Test desc 1') + ), + expected=errors.DuplicateEntry(message=u'sudo command group ' + + u'with name "%s" already exists' % sudocmdgroup1), + ), + + + dict( + desc='Retrieve %r' % sudocmdgroup1, + command=('sudocmdgroup_show', [sudocmdgroup1], {}), + expected=dict( + value=sudocmdgroup1, + summary=None, + result=dict( + cn=[sudocmdgroup1], + description=[u'Test desc 1'], + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Updated %r' % sudocmdgroup1, + command=( + 'sudocmdgroup_mod', [sudocmdgroup1], + dict(description=u'New desc 1') + ), + expected=dict( + result=dict( + cn=[sudocmdgroup1], + description=[u'New desc 1'], + ), + summary=u'Modified Sudo Command Group "testsudocmdgroup1"', + value=sudocmdgroup1, + ), + ), + + + dict( + desc='Retrieve %r to verify update' % sudocmdgroup1, + command=('sudocmdgroup_show', [sudocmdgroup1], {}), + expected=dict( + value=sudocmdgroup1, + result=dict( + cn=[sudocmdgroup1], + description=[u'New desc 1'], + dn=DN(('cn','testsudocmdgroup1'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % sudocmdgroup1, + command=('sudocmdgroup_find', [], dict(cn=sudocmdgroup1)), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + dn=DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + cn=[sudocmdgroup1], + description=[u'New desc 1'], + ), + ], + summary=u'1 Sudo Command Group matched', + ), + ), + + + + ################ + # create sudocmdgroup2: + dict( + desc='Try to retrieve non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_show', [sudocmdgroup2], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_mod', [sudocmdgroup2], + dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_del', [sudocmdgroup2], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + dict( + desc='Create %r' % sudocmdgroup2, + command=( + 'sudocmdgroup_add', [sudocmdgroup2], + dict(description=u'Test desc 2') + ), + expected=dict( + value=sudocmdgroup2, + summary=u'Added Sudo Command Group "testsudocmdgroup2"', + result=dict( + cn=[sudocmdgroup2], + description=[u'Test desc 2'], + objectclass=objectclasses.sudocmdgroup, + ipauniqueid=[fuzzy_uuid], + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % sudocmdgroup2, + command=( + 'sudocmdgroup_add', [sudocmdgroup2], + dict(description=u'Test desc 2') + ), + expected=errors.DuplicateEntry( + message=u'sudo command group with name "%s" already exists' % + sudocmdgroup2), + ), + + + dict( + desc='Retrieve %r' % sudocmdgroup2, + command=('sudocmdgroup_show', [sudocmdgroup2], {}), + expected=dict( + value=sudocmdgroup2, + summary=None, + result=dict( + cn=[sudocmdgroup2], + description=[u'Test desc 2'], + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + ), + ), + + + dict( + desc='Updated %r' % sudocmdgroup2, + command=( + 'sudocmdgroup_mod', [sudocmdgroup2], + dict(description=u'New desc 2') + ), + expected=dict( + result=dict( + cn=[sudocmdgroup2], + description=[u'New desc 2'], + ), + summary=u'Modified Sudo Command Group "testsudocmdgroup2"', + value=sudocmdgroup2, + ), + ), + + + dict( + desc='Retrieve %r to verify update' % sudocmdgroup2, + command=('sudocmdgroup_show', [sudocmdgroup2], {}), + expected=dict( + value=sudocmdgroup2, + result=dict( + cn=[sudocmdgroup2], + description=[u'New desc 2'], + dn=DN(('cn','testsudocmdgroup2'),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + ), + summary=None, + ), + ), + + + dict( + desc='Search for %r' % sudocmdgroup2, + command=('sudocmdgroup_find', [], dict(cn=sudocmdgroup2)), + expected=dict( + count=1, + truncated=False, + result=[ + dict( + dn=DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + cn=[sudocmdgroup2], + description=[u'New desc 2'], + ), + ], + summary=u'1 Sudo Command Group matched', + ), + ), + + + dict( + desc='Search for all sudocmdgroups', + command=('sudocmdgroup_find', [], {}), + expected=dict( + summary=u'2 Sudo Command Groups matched', + count=2, + truncated=False, + result=[ + dict( + dn=DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + cn=[sudocmdgroup1], + description=[u'New desc 1'], + ), + dict( + dn=DN(('cn',sudocmdgroup2),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + cn=[sudocmdgroup2], + description=[u'New desc 2'], + ), + ], + ), + ), + + + + ############### + # member stuff: + dict( + desc='Add member %r to %r' % (sudocmd1, sudocmdgroup1), + command=( + 'sudocmdgroup_add_member', [sudocmdgroup1], + dict(sudocmd=sudocmd1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'member_sudocmd': (sudocmd1,), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Retrieve %r to show membership' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=dict( + value=sudocmd1, + summary=None, + result=dict( + dn=fuzzy_sudocmddn, + sudocmd=[sudocmd1], + description=[u'Test sudo command 1'], + memberof_sudocmdgroup=[u'testsudocmdgroup1'], + ), + ), + ), + + dict( + desc='Try to add non-existent member to %r' % sudocmdgroup1, + command=( + 'sudocmdgroup_add_member', [sudocmdgroup1], + dict(sudocmd=u'notfound') + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + sudocmd=[(u'notfound', u'no such entry')], + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'member_sudocmd': (u'/usr/bin/sudotestcmd1',), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Add member %r to %r' % (sudocmd1_camelcase, sudocmdgroup1), + command=( + 'sudocmdgroup_add_member', [sudocmdgroup1], + dict(sudocmd=sudocmd1_camelcase) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'member_sudocmd': (sudocmd1, sudocmd1_camelcase), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Remove member %r from %r' % (sudocmd1, sudocmdgroup1), + command=('sudocmdgroup_remove_member', + [sudocmdgroup1], dict(sudocmd=sudocmd1) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'member_sudocmd': (sudocmd1_camelcase,), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Remove member %r from %r' % (sudocmd1_camelcase, sudocmdgroup1), + command=('sudocmdgroup_remove_member', + [sudocmdgroup1], dict(sudocmd=sudocmd1_camelcase) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + # FIXME: Shouldn't this raise a NotFound instead? + desc='Try to remove non-existent member from %r' % sudocmdgroup1, + command=('sudocmdgroup_remove_member', + [sudocmdgroup1], dict(sudocmd=u'notfound') + ), + expected=dict( + completed=0, + failed=dict( + member=dict( + sudocmd=[(u'notfound', u'This entry is not a member')], + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + ################ + # test a command that needs DN escaping: + create_command(sudocmd_plus), + + dict( + desc='Add %r to %r' % (sudocmd_plus, sudocmdgroup1), + command=('sudocmdgroup_add_member', [sudocmdgroup1], + dict(sudocmd=sudocmd_plus) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'member_sudocmd': (sudocmd_plus,), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + dict( + desc='Remove %r from %r' % (sudocmd_plus, sudocmdgroup1), + command=('sudocmdgroup_remove_member', [sudocmdgroup1], + dict(sudocmd=sudocmd_plus) + ), + expected=dict( + completed=1, + failed=dict( + member=dict( + sudocmd=tuple(), + ), + ), + result={ + 'dn': DN(('cn',sudocmdgroup1),('cn','sudocmdgroups'), + ('cn','sudo'),api.env.basedn), + 'cn': [sudocmdgroup1], + 'description': [u'New desc 1'], + }, + ), + ), + + ################ + # delete sudocmdgroup1: + dict( + desc='Delete %r' % sudocmdgroup1, + command=('sudocmdgroup_del', [sudocmdgroup1], {}), + expected=dict( + result=dict(failed=u''), + value=sudocmdgroup1, + summary=u'Deleted Sudo Command Group "testsudocmdgroup1"', + ) + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_del', [sudocmdgroup1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + dict( + desc='Try to retrieve non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_show', [sudocmdgroup1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmdgroup1, + command=('sudocmdgroup_mod', [sudocmdgroup1], + dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup1), + ), + + + ################ + # delete sudocmdgroup2: + dict( + desc='Delete %r' % sudocmdgroup2, + command=('sudocmdgroup_del', [sudocmdgroup2], {}), + expected=dict( + result=dict(failed=u''), + value=sudocmdgroup2, + summary=u'Deleted Sudo Command Group "testsudocmdgroup2"', + ) + ), + + + dict( + desc='Try to delete non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_del', [sudocmdgroup2], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + dict( + desc='Try to retrieve non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_show', [sudocmdgroup2], {}), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + dict( + desc='Try to update non-existent %r' % sudocmdgroup2, + command=('sudocmdgroup_mod', [sudocmdgroup2], + dict(description=u'Foo')), + expected=errors.NotFound( + reason=u'%s: sudo command group not found' % sudocmdgroup2), + ), + + + ##### clean up test Command + + dict( + desc='Now delete the sudo command %r' % sudocmd1, + command=('sudocmd_del', [sudocmd1], {}), + expected=dict( + result=dict(failed=u''), + value=sudocmd1, + summary=u'Deleted Sudo Command "%s"' % sudocmd1, + ) + ), + + + dict( + desc='Verify that %r is really gone' % sudocmd1, + command=('sudocmd_show', [sudocmd1], {}), + expected=errors.NotFound( + reason=u'%s: sudo command not found' % sudocmd1), + ), + + ] diff --git a/ipatests/test_xmlrpc/test_sudorule_plugin.py b/ipatests/test_xmlrpc/test_sudorule_plugin.py new file mode 100644 index 000000000..ec5d16d62 --- /dev/null +++ b/ipatests/test_xmlrpc/test_sudorule_plugin.py @@ -0,0 +1,781 @@ +# Authors: +# Jr Aquino <jr.aquino@citrixonline.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/sudorule.py` module. +""" + +from nose.tools import raises, assert_raises # pylint: disable=E0611 + +from xmlrpc_test import XMLRPC_test, assert_attr_equal +from ipalib import api +from ipalib import errors + +class test_sudorule(XMLRPC_test): + """ + Test the `sudorule` plugin. + """ + rule_name = u'testing_sudorule1' + rule_name2 = u'testing_sudorule2' + rule_command = u'/usr/bin/testsudocmd1' + rule_desc = u'description' + rule_desc_mod = u'description modified' + + test_user = u'sudorule_test_user' + test_external_user = u'external_test_user' + test_group = u'sudorule_test_group' + test_external_group = u'external_test_group' + test_host = u'sudorule.testhost' + test_external_host = u'external.testhost' + test_hostgroup = u'sudorule_test_hostgroup' + test_sudoallowcmdgroup = u'sudorule_test_allowcmdgroup' + test_sudodenycmdgroup = u'sudorule_test_denycmdgroup' + test_command = u'/usr/bin/testsudocmd1' + test_denycommand = u'/usr/bin/testdenysudocmd1' + test_runasuser = u'manager' + test_runasgroup = u'manager' + test_category = u'all' + test_option = u'authenticate' + + test_invalid_user = u'+invalid#user' + test_invalid_host = u'+invalid&host.nonexist.com' + test_invalid_group = u'+invalid#group' + + def test_0_sudorule_add(self): + """ + Test adding a new Sudo rule using `xmlrpc.sudorule_add`. + """ + ret = self.failsafe_add(api.Object.sudorule, + self.rule_name, + description=self.rule_desc, + ) + entry = ret['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'description', self.rule_desc) + + @raises(errors.DuplicateEntry) + def test_1_sudorule_add(self): + """ + Test adding an duplicate Sudo rule using `xmlrpc.sudorule_add'. + """ + api.Command['sudorule_add']( + self.rule_name + ) + + def test_2_sudorule_show(self): + """ + Test displaying a Sudo rule using `xmlrpc.sudorule_show`. + """ + entry = api.Command['sudorule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'description', self.rule_desc) + + def test_3_sudorule_mod(self): + """ + Test modifying a Sudo rule using `xmlrpc.sudorule_mod`. + """ + ret = api.Command['sudorule_mod']( + self.rule_name, description=self.rule_desc_mod + ) + entry = ret['result'] + assert_attr_equal(entry, 'description', self.rule_desc_mod) + + def test_6_sudorule_find(self): + """ + Test searching for Sudo rules using `xmlrpc.sudorule_find`. + """ + ret = api.Command['sudorule_find']( + cn=self.rule_name, + description=self.rule_desc_mod + ) + assert ret['truncated'] is False + entries = ret['result'] + assert_attr_equal(entries[0], 'cn', self.rule_name) + assert_attr_equal(entries[0], 'description', self.rule_desc_mod) + + def test_7_sudorule_init_testing_data(self): + """ + Initialize data for more Sudo rule plugin testing. + """ + self.failsafe_add(api.Object.user, + self.test_user, givenname=u'first', sn=u'last' + ) + self.failsafe_add(api.Object.user, + self.test_runasuser, givenname=u'first', sn=u'last' + ) + self.failsafe_add(api.Object.group, + self.test_group, description=u'description' + ) + self.failsafe_add(api.Object.host, + self.test_host, force=True + ) + self.failsafe_add(api.Object.hostgroup, + self.test_hostgroup, description=u'description' + ) + self.failsafe_add(api.Object.sudocmdgroup, + self.test_sudoallowcmdgroup, description=u'desc' + ) + self.failsafe_add(api.Object.sudocmdgroup, + self.test_sudodenycmdgroup, description=u'desc' + ) + self.failsafe_add(api.Object.sudocmd, + self.test_command, description=u'desc' + ) + + def test_8_sudorule_add_user(self): + """ + Test adding user and group to Sudo rule using + `xmlrpc.sudorule_add_user`. + """ + ret = api.Command['sudorule_add_user']( + self.rule_name, user=self.test_user, group=self.test_group + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert not failed['memberuser']['user'] + assert 'group' in failed['memberuser'] + assert not failed['memberuser']['group'] + entry = ret['result'] + assert_attr_equal(entry, 'memberuser_user', self.test_user) + assert_attr_equal(entry, 'memberuser_group', self.test_group) + + def test_9_a_show_user(self): + """ + Test showing a user to verify Sudo rule membership + `xmlrpc.user_show`. + """ + ret = api.Command['user_show'](self.test_user, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_sudorule', self.rule_name) + + def test_9_b_show_group(self): + """ + Test showing a group to verify Sudo rule membership + `xmlrpc.group_show`. + """ + ret = api.Command['group_show'](self.test_group, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_sudorule', self.rule_name) + + def test_9_sudorule_remove_user(self): + """ + Test removing user and group from Sudo rule using + `xmlrpc.sudorule_remove_user'. + """ + ret = api.Command['sudorule_remove_user']( + self.rule_name, user=self.test_user, group=self.test_group + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert not failed['memberuser']['user'] + assert 'group' in failed['memberuser'] + assert not failed['memberuser']['group'] + entry = ret['result'] + assert 'memberuser_user' not in entry + assert 'memberuser_group' not in entry + + def test_a_sudorule_add_runasuser(self): + """ + Test adding run as user to Sudo rule using + `xmlrpc.sudorule_add_runasuser`. + """ + ret = api.Command['sudorule_add_runasuser']( + self.rule_name, user=self.test_runasuser + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'ipasudorunas' in failed + assert 'user' in failed['ipasudorunas'] + assert not failed['ipasudorunas']['user'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunas_user', self.test_runasuser) + + def test_a_sudorule_add_runasuser_invalid(self): + """ + Test adding run as invalid user to Sudo rule using + `xmlrpc.sudorule_add_runasuser`. + """ + try: + api.Command['sudorule_add_runasuser']( + self.rule_name, user=self.test_invalid_user + ) + except errors.ValidationError: + pass + else: + assert False + + def test_b_sudorule_remove_runasuser(self): + """ + Test removing run as user to Sudo rule using + `xmlrpc.sudorule_remove_runasuser'. + """ + ret = api.Command['sudorule_remove_runasuser']( + self.rule_name, user=self.test_runasuser + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'ipasudorunas' in failed + assert 'user' in failed['ipasudorunas'] + assert not failed['ipasudorunas']['user'] + entry = ret['result'] + assert 'ipasudorunas_user' not in entry + + def test_a_sudorule_add_runasgroup(self): + """ + Test adding run as group to Sudo rule using + `xmlrpc.sudorule_add_runasgroup`. + """ + ret = api.Command['sudorule_add_runasgroup']( + self.rule_name, group=self.test_runasgroup + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'ipasudorunasgroup' in failed + assert 'group' in failed['ipasudorunasgroup'] + assert not failed['ipasudorunasgroup']['group'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunasgroup_group', + self.test_runasgroup) + + def test_a_sudorule_add_runasgroup_invalid(self): + """ + Test adding run as invalid user to Sudo rule using + `xmlrpc.sudorule_add_runasuser`. + """ + try: + api.Command['sudorule_add_runasgroup']( + self.rule_name, group=self.test_invalid_group + ) + except errors.ValidationError: + pass + else: + assert False + + def test_b_sudorule_remove_runasgroup(self): + """ + Test removing run as group to Sudo rule using + `xmlrpc.sudorule_remove_runasgroup'. + """ + ret = api.Command['sudorule_remove_runasgroup']( + self.rule_name, group=self.test_runasgroup + ) + assert ret['completed'] == 1 + failed = ret['failed'] + assert 'ipasudorunasgroup' in failed + assert 'group' in failed['ipasudorunasgroup'] + assert not failed['ipasudorunasgroup']['group'] + entry = ret['result'] + assert 'ipasudorunasgroup_group' not in entry + + def test_a_sudorule_add_externaluser(self): + """ + Test adding an external user to Sudo rule using + `xmlrpc.sudorule_add_user`. + """ + ret = api.Command['sudorule_add_user']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'externaluser', self.test_external_user) + + def test_a_sudorule_add_externaluser_invalid(self): + """ + Test adding an invalid external user to Sudo rule using + `xmlrpc.sudorule_add_user`. + """ + try: + api.Command['sudorule_add_user']( + self.rule_name, user=self.test_invalid_user + ) + except errors.ValidationError: + pass + else: + assert False + + def test_b_sudorule_remove_externaluser(self): + """ + Test removing an external user from Sudo rule using + `xmlrpc.sudorule_remove_user'. + """ + ret = api.Command['sudorule_remove_user']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert entry['externaluser'] == () + + def test_a_sudorule_add_runasexternaluser(self): + """ + Test adding an external runasuser to Sudo rule using + `xmlrpc.sudorule_add_runasuser`. + """ + ret = api.Command['sudorule_add_runasuser']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunasextuser', self.test_external_user) + + def test_b_sudorule_remove_runasexternaluser(self): + """ + Test removing an external runasuser from Sudo rule using + `xmlrpc.sudorule_remove_runasuser'. + """ + ret = api.Command['sudorule_remove_runasuser']( + self.rule_name, user=self.test_external_user + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert entry['ipasudorunasextuser'] == () + + def test_a_sudorule_add_runasexternalgroup(self): + """ + Test adding an external runasgroup to Sudo rule using + `xmlrpc.sudorule_add_runasgroup`. + """ + ret = api.Command['sudorule_add_runasgroup']( + self.rule_name, group=self.test_external_group + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'ipasudorunasextgroup', self.test_external_group) + + def test_b_sudorule_remove_runasexternalgroup(self): + """ + Test removing an external runasgroup from Sudo rule using + `xmlrpc.sudorule_remove_runasgroup'. + """ + ret = api.Command['sudorule_remove_runasgroup']( + self.rule_name, group=self.test_external_group + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert entry['ipasudorunasextgroup'] == () + + def test_a_sudorule_add_option(self): + """ + Test adding an option to Sudo rule using + `xmlrpc.sudorule_add_option`. + """ + ret = api.Command['sudorule_add_option']( + self.rule_name, ipasudoopt=self.test_option + ) + entry = ret['result'] + assert_attr_equal(entry, 'ipasudoopt', self.test_option) + + def test_b_sudorule_remove_option(self): + """ + Test removing an option from Sudo rule using + `xmlrpc.sudorule_remove_option'. + """ + ret = api.Command['sudorule_remove_option']( + self.rule_name, ipasudoopt=self.test_option + ) + entry = ret['result'] + assert 'ipasudoopt' not in entry + + def test_a_sudorule_add_host(self): + """ + Test adding host and hostgroup to Sudo rule using + `xmlrpc.sudorule_add_host`. + """ + ret = api.Command['sudorule_add_host']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert not failed['memberhost']['host'] + assert 'hostgroup' in failed['memberhost'] + assert not failed['memberhost']['hostgroup'] + entry = ret['result'] + assert_attr_equal(entry, 'memberhost_host', self.test_host) + assert_attr_equal(entry, 'memberhost_hostgroup', self.test_hostgroup) + + def test_a_sudorule_show_host(self): + """ + Test showing host to verify Sudo rule membership + `xmlrpc.host_show`. + """ + ret = api.Command['host_show'](self.test_host, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_sudorule', self.rule_name) + + def test_a_sudorule_show_hostgroup(self): + """ + Test showing hostgroup to verify Sudo rule membership + `xmlrpc.hostgroup_show`. + """ + ret = api.Command['hostgroup_show'](self.test_hostgroup, all=True) + entry = ret['result'] + assert_attr_equal(entry, 'memberof_sudorule', self.rule_name) + + def test_b_sudorule_remove_host(self): + """ + Test removing host and hostgroup from Sudo rule using + `xmlrpc.sudorule_remove_host`. + """ + ret = api.Command['sudorule_remove_host']( + self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert not failed['memberhost']['host'] + assert 'hostgroup' in failed['memberhost'] + assert not failed['memberhost']['hostgroup'] + entry = ret['result'] + assert 'memberhost_host' not in entry + assert 'memberhost_hostgroup' not in entry + + def test_a_sudorule_add_externalhost(self): + """ + Test adding an external host to Sudo rule using + `xmlrpc.sudorule_add_host`. + """ + ret = api.Command['sudorule_add_host']( + self.rule_name, host=self.test_external_host + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert_attr_equal(entry, 'externalhost', self.test_external_host) + + def test_a_sudorule_add_externalhost_invalid(self): + """ + Test adding an invalid external host to Sudo rule using + `xmlrpc.sudorule_add_host`. + """ + try: + api.Command['sudorule_add_host']( + self.rule_name, host=self.test_invalid_host + ) + except errors.ValidationError: + pass + else: + assert False + + def test_a_sudorule_mod_externalhost_invalid_addattr(self): + """ + Test adding an invalid external host to Sudo rule using + `xmlrpc.sudorule_mod --addattr`. + """ + try: + api.Command['sudorule_mod']( + self.rule_name, + addattr='externalhost=%s' % self.test_invalid_host + ) + except errors.ValidationError, e: + assert unicode(e) == ("invalid 'externalhost': only letters, " + + "numbers, _, and - are allowed. " + + "DNS label may not start or end with -") + else: + assert False + + def test_b_sudorule_remove_externalhost(self): + """ + Test removing an external host from Sudo rule using + `xmlrpc.sudorule_remove_host`. + """ + ret = api.Command['sudorule_remove_host']( + self.rule_name, host=self.test_external_host + ) + assert ret['completed'] == 1 + failed = ret['failed'] + entry = ret['result'] + assert len(entry['externalhost']) == 0 + + def test_a_sudorule_add_allow_command(self): + """ + Test adding allow command and cmdgroup to Sudo rule using + `xmlrpc.sudorule_add_allow_command`. + """ + ret = api.Command['sudorule_add_allow_command']( + self.rule_name, sudocmd=self.test_command, + sudocmdgroup=self.test_sudoallowcmdgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberallowcmd' in failed + assert 'sudocmd' in failed['memberallowcmd'] + assert not failed['memberallowcmd']['sudocmd'] + assert 'sudocmdgroup' in failed['memberallowcmd'] + assert not failed['memberallowcmd']['sudocmdgroup'] + entry = ret['result'] + assert_attr_equal(entry, 'memberallowcmd_sudocmd', self.test_command) + assert_attr_equal(entry, 'memberallowcmd_sudocmdgroup', + self.test_sudoallowcmdgroup) + + def test_a_sudorule_remove_allow_command(self): + """ + Test removing allow command and sudocmdgroup from Sudo rule using + `xmlrpc.sudorule_remove_command`. + """ + ret = api.Command['sudorule_remove_allow_command']( + self.rule_name, sudocmd=self.test_command, + sudocmdgroup=self.test_sudoallowcmdgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberallowcmd' in failed + assert 'sudocmd' in failed['memberallowcmd'] + assert not failed['memberallowcmd']['sudocmd'] + assert 'sudocmdgroup' in failed['memberallowcmd'] + assert not failed['memberallowcmd']['sudocmdgroup'] + entry = ret['result'] + assert 'memberallowcmd_sudocmd' not in entry + assert 'memberallowcmd_sudocmdgroup' not in entry + + def test_b_sudorule_add_deny_command(self): + """ + Test adding deny command and cmdgroup to Sudo rule using + `xmlrpc.sudorule_add_deny_command`. + """ + ret = api.Command['sudorule_add_deny_command']( + self.rule_name, sudocmd=self.test_command, + sudocmdgroup=self.test_sudodenycmdgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberdenycmd' in failed + assert 'sudocmd' in failed['memberdenycmd'] + assert not failed['memberdenycmd']['sudocmd'] + assert 'sudocmdgroup' in failed['memberdenycmd'] + assert not failed['memberdenycmd']['sudocmdgroup'] + entry = ret['result'] + assert_attr_equal(entry, 'memberdenycmd_sudocmd', self.test_command) + assert_attr_equal(entry, 'memberdenycmd_sudocmdgroup', + self.test_sudodenycmdgroup) + + def test_b_sudorule_remove_deny_command(self): + """ + Test removing deny command and sudocmdgroup from Sudo rule using + `xmlrpc.sudorule_remove_deny_command`. + """ + ret = api.Command['sudorule_remove_deny_command']( + self.rule_name, sudocmd=self.test_command, + sudocmdgroup=self.test_sudodenycmdgroup + ) + assert ret['completed'] == 2 + failed = ret['failed'] + assert 'memberdenycmd' in failed + assert 'sudocmd' in failed['memberdenycmd'] + assert not failed['memberdenycmd']['sudocmd'] + assert 'sudocmdgroup' in failed['memberdenycmd'] + assert not failed['memberdenycmd']['sudocmdgroup'] + entry = ret['result'] + assert 'memberdenycmd_sudocmd' not in entry + assert 'memberdenycmd_sudocmdgroup' not in entry + + @raises(errors.MutuallyExclusiveError) + def test_c_sudorule_exclusiveuser(self): + """ + Test adding a user to an Sudo rule when usercat='all' + """ + api.Command['sudorule_mod'](self.rule_name, usercategory=u'all') + try: + api.Command['sudorule_add_user'](self.rule_name, user=u'admin') + finally: + api.Command['sudorule_mod'](self.rule_name, usercategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_d_sudorule_exclusiveuser(self): + """ + Test setting usercat='all' in an Sudo rule when there are users + """ + api.Command['sudorule_add_user'](self.rule_name, user=u'admin') + try: + api.Command['sudorule_mod'](self.rule_name, usercategory=u'all') + finally: + api.Command['sudorule_remove_user'](self.rule_name, user=u'admin') + + @raises(errors.MutuallyExclusiveError) + def test_e_sudorule_exclusivehost(self): + """ + Test adding a host to an Sudo rule when hostcat='all' + """ + api.Command['sudorule_mod'](self.rule_name, hostcategory=u'all') + try: + api.Command['sudorule_add_host'](self.rule_name, host=self.test_host) + finally: + api.Command['sudorule_mod'](self.rule_name, hostcategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_f_sudorule_exclusivehost(self): + """ + Test setting hostcat='all' in an Sudo rule when there are hosts + """ + api.Command['sudorule_add_host'](self.rule_name, host=self.test_host) + try: + api.Command['sudorule_mod'](self.rule_name, hostcategory=u'all') + finally: + api.Command['sudorule_remove_host'](self.rule_name, host=self.test_host) + + @raises(errors.MutuallyExclusiveError) + def test_g_sudorule_exclusivecommand(self): + """ + Test adding a command to an Sudo rule when cmdcategory='all' + """ + api.Command['sudorule_mod'](self.rule_name, cmdcategory=u'all') + try: + api.Command['sudorule_add_allow_command'](self.rule_name, sudocmd=self.test_command) + finally: + api.Command['sudorule_mod'](self.rule_name, cmdcategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_h_sudorule_exclusivecommand(self): + """ + Test setting cmdcategory='all' in an Sudo rule when there are commands + """ + api.Command['sudorule_add_allow_command'](self.rule_name, sudocmd=self.test_command) + try: + api.Command['sudorule_mod'](self.rule_name, cmdcategory=u'all') + finally: + api.Command['sudorule_remove_allow_command'](self.rule_name, sudocmd=self.test_command) + + @raises(errors.MutuallyExclusiveError) + def test_i_sudorule_exclusiverunas(self): + """ + Test adding a runasuser to an Sudo rule when ipasudorunasusercategory='all' + """ + api.Command['sudorule_mod'](self.rule_name, ipasudorunasusercategory=u'all') + try: + api.Command['sudorule_add_runasuser'](self.rule_name, user=self.test_user) + finally: + api.Command['sudorule_mod'](self.rule_name, ipasudorunasusercategory=u'') + + @raises(errors.MutuallyExclusiveError) + def test_j_1_sudorule_exclusiverunas(self): + """ + Test setting ipasudorunasusercategory='all' in an Sudo rule when there are runas users + """ + api.Command['sudorule_add_runasuser'](self.rule_name, user=self.test_user) + try: + api.Command['sudorule_mod'](self.rule_name, ipasudorunasusercategory=u'all') + finally: + api.Command['sudorule_remove_runasuser'](self.rule_name, user=self.test_command) + + def test_j_2_sudorule_referential_integrity(self): + """ + Test adding various links to Sudo rule + """ + api.Command['sudorule_add_user'](self.rule_name, user=self.test_user) + api.Command['sudorule_add_runasuser'](self.rule_name, user=self.test_runasuser, + group=self.test_group) + api.Command['sudorule_add_runasgroup'](self.rule_name, group=self.test_group) + api.Command['sudorule_add_host'](self.rule_name, host=self.test_host) + api.Command['sudorule_add_allow_command'](self.rule_name, + sudocmd=self.test_command) + api.Command['sudorule_add_deny_command'](self.rule_name, + sudocmdgroup=self.test_sudodenycmdgroup) + entry = api.Command['sudorule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert_attr_equal(entry, 'memberuser_user', self.test_user) + assert_attr_equal(entry, 'memberallowcmd_sudocmd', self.test_command) + assert_attr_equal(entry, 'memberdenycmd_sudocmdgroup', + self.test_sudodenycmdgroup) + assert_attr_equal(entry, 'memberhost_host', self.test_host) + assert_attr_equal(entry, 'ipasudorunas_user', self.test_runasuser) + assert_attr_equal(entry, 'ipasudorunas_group', self.test_group) + assert_attr_equal(entry, 'ipasudorunasgroup_group', self.test_group) + + + def test_k_1_sudorule_clear_testing_data(self): + """ + Clear data for Sudo rule plugin testing. + """ + api.Command['user_del'](self.test_user) + api.Command['user_del'](self.test_runasuser) + api.Command['group_del'](self.test_group) + api.Command['host_del'](self.test_host) + api.Command['hostgroup_del'](self.test_hostgroup) + api.Command['sudorule_remove_allow_command'](self.rule_name, + sudocmd=self.test_command) + api.Command['sudocmd_del'](self.test_command) + api.Command['sudocmdgroup_del'](self.test_sudoallowcmdgroup) + api.Command['sudocmdgroup_del'](self.test_sudodenycmdgroup) + + def test_k_2_sudorule_referential_integrity(self): + """ + Test that links in Sudo rule were removed by referential integrity plugin + """ + entry = api.Command['sudorule_show'](self.rule_name)['result'] + assert_attr_equal(entry, 'cn', self.rule_name) + assert 'memberuser_user' not in entry + assert 'memberallowcmd_sudocmd' not in entry + assert 'memberdenycmd_sudocmdgroup' not in entry + assert 'memberhost_host' not in entry + assert 'ipasudorunas_user' not in entry + assert 'ipasudorunas_group' not in entry + assert 'ipasudorunasgroup_group' not in entry + + def test_l_sudorule_order(self): + """ + Test that order uniqueness is maintained + """ + api.Command['sudorule_mod'](self.rule_name, sudoorder=1) + + api.Command['sudorule_add'](self.rule_name2) + + # mod of rule that has no order and set a duplicate + try: + api.Command['sudorule_mod'](self.rule_name2, sudoorder=1) + except errors.ValidationError: + pass + + # Remove the rule so we can re-add it + api.Command['sudorule_del'](self.rule_name2) + + # add a new rule with a duplicate order + with assert_raises(errors.ValidationError): + api.Command['sudorule_add'](self.rule_name2, sudoorder=1) + + # add a new rule with a unique order + api.Command['sudorule_add'](self.rule_name2, sudoorder=2) + with assert_raises(errors.ValidationError): + api.Command['sudorule_mod'](self.rule_name2, sudoorder=1) + + # Try setting both to 0 + api.Command['sudorule_mod'](self.rule_name2, sudoorder=0) + with assert_raises(errors.ValidationError): + api.Command['sudorule_mod'](self.rule_name, sudoorder=0) + + + def test_m_sudorule_del(self): + """ + Test deleting a Sudo rule using `xmlrpc.sudorule_del`. + """ + api.Command['sudorule_del'](self.rule_name) + # verify that it's gone + with assert_raises(errors.NotFound): + api.Command['sudorule_show'](self.rule_name) + api.Command['sudorule_del'](self.rule_name2) diff --git a/ipatests/test_xmlrpc/test_trust_plugin.py b/ipatests/test_xmlrpc/test_trust_plugin.py new file mode 100644 index 000000000..0223e8b36 --- /dev/null +++ b/ipatests/test_xmlrpc/test_trust_plugin.py @@ -0,0 +1,159 @@ +# Authors: +# Martin Kosek <mkosek@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test the `ipalib/plugins/trust.py` module. +""" + +import nose +from ipalib import api, errors +from ipapython.dn import DN +from ipatests.test_xmlrpc import objectclasses +from xmlrpc_test import (Declarative, fuzzy_guid, fuzzy_domain_sid, fuzzy_string, + fuzzy_uuid, fuzzy_digits) + + +trustconfig_ad_config = DN(('cn', api.env.domain), + api.env.container_cifsdomains, api.env.basedn) +testgroup = u'adtestgroup' +testgroup_dn = DN(('cn', testgroup), api.env.container_group, api.env.basedn) + +default_group = u'Default SMB Group' +default_group_dn = DN(('cn', default_group), api.env.container_group, api.env.basedn) + +class test_trustconfig(Declarative): + + @classmethod + def setUpClass(cls): + super(test_trustconfig, cls).setUpClass() + if not api.Backend.xmlclient.isconnected(): + api.Backend.xmlclient.connect(fallback=False) + try: + api.Command['trustconfig_show'](trust_type=u'ad') + except errors.NotFound: + raise nose.SkipTest('Trusts are not configured') + + cleanup_commands = [ + ('group_del', [testgroup], {}), + ('trustconfig_mod', [], {'trust_type': u'ad', + 'ipantfallbackprimarygroup': default_group}), + ] + + tests = [ + + dict( + desc='Retrieve trust configuration for AD domains', + command=('trustconfig_show', [], {'trust_type': u'ad'}), + expected={ + 'value': u'ad', + 'summary': None, + 'result': { + 'dn': trustconfig_ad_config, + 'cn': [api.env.domain], + 'ipantdomainguid': [fuzzy_guid], + 'ipantfallbackprimarygroup': [default_group], + 'ipantflatname': [fuzzy_string], + 'ipantsecurityidentifier': [fuzzy_domain_sid] + }, + }, + ), + + dict( + desc='Retrieve trust configuration for AD domains with --raw', + command=('trustconfig_show', [], {'trust_type': u'ad', 'raw': True}), + expected={ + 'value': u'ad', + 'summary': None, + 'result': { + 'dn': trustconfig_ad_config, + 'cn': [api.env.domain], + 'ipantdomainguid': [fuzzy_guid], + 'ipantfallbackprimarygroup': [default_group_dn], + 'ipantflatname': [fuzzy_string], + 'ipantsecurityidentifier': [fuzzy_domain_sid] + }, + }, + ), + + dict( + desc='Create auxiliary group %r' % testgroup, + command=( + 'group_add', [testgroup], dict(description=u'Test group') + ), + expected=dict( + value=testgroup, + summary=u'Added group "%s"' % testgroup, + result=dict( + cn=[testgroup], + description=[u'Test group'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=testgroup_dn, + ), + ), + ), + + dict( + desc='Try to change primary fallback group to nonexistent group', + command=('trustconfig_mod', [], + {'trust_type': u'ad', 'ipantfallbackprimarygroup': u'doesnotexist'}), + expected=errors.NotFound(reason=u'%s: group not found' % 'doesnotexist') + ), + + dict( + desc='Try to change primary fallback group to nonexistent group DN', + command=('trustconfig_mod', [], {'trust_type': u'ad', + 'ipantfallbackprimarygroup': u'cn=doesnotexist,dc=test'}), + expected=errors.NotFound(reason=u'%s: group not found' % 'cn=doesnotexist,dc=test') + ), + + dict( + desc='Change primary fallback group to "%s"' % testgroup, + command=('trustconfig_mod', [], {'trust_type': u'ad', + 'ipantfallbackprimarygroup': testgroup}), + expected={ + 'value': u'ad', + 'summary': u'Modified "ad" trust configuration', + 'result': { + 'cn': [api.env.domain], + 'ipantdomainguid': [fuzzy_guid], + 'ipantfallbackprimarygroup': [testgroup], + 'ipantflatname': [fuzzy_string], + 'ipantsecurityidentifier': [fuzzy_domain_sid] + }, + }, + ), + + dict( + desc='Change primary fallback group back to "%s" using DN' % default_group, + command=('trustconfig_mod', [], {'trust_type': u'ad', + 'ipantfallbackprimarygroup': unicode(default_group_dn)}), + expected={ + 'value': u'ad', + 'summary': u'Modified "ad" trust configuration', + 'result': { + 'cn': [api.env.domain], + 'ipantdomainguid': [fuzzy_guid], + 'ipantfallbackprimarygroup': [default_group], + 'ipantflatname': [fuzzy_string], + 'ipantsecurityidentifier': [fuzzy_domain_sid] + }, + }, + ), + ] diff --git a/ipatests/test_xmlrpc/test_user_plugin.py b/ipatests/test_xmlrpc/test_user_plugin.py new file mode 100644 index 000000000..ca6ff16c6 --- /dev/null +++ b/ipatests/test_xmlrpc/test_user_plugin.py @@ -0,0 +1,1837 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# Jason Gerard DeRose <jderose@redhat.com> +# +# Copyright (C) 2008, 2009 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib/plugins/user.py` module. +""" + +from ipalib import api, errors, messages +from ipatests.test_xmlrpc import objectclasses +from ipatests.util import assert_equal, assert_not_equal +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_password, fuzzy_string, fuzzy_dergeneralizedtime +from ipapython.dn import DN +from ipapython.version import API_VERSION + +user1=u'tuser1' +user2=u'tuser2' +admin1=u'admin' +admin2=u'admin2' +renameduser1=u'tuser' +group1=u'group1' +admins_group=u'admins' + +invaliduser1=u'+tuser1' +invaliduser2=u'tuser1234567890123456789012345678901234567890' + +sshpubkey = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGAX3xAeLeaJggwTqMjxNwa6XHBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGIwA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNmcSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM019Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF0L public key test' +sshpubkeyfp = u'13:67:6B:BF:4E:A2:05:8E:AE:25:8B:A1:31:DE:6F:1B public key test (ssh-rsa)' + +def get_user_dn(uid): + return DN(('uid', uid), api.env.container_user, api.env.basedn) + +def get_group_dn(cn): + return DN(('cn', cn), api.env.container_group, api.env.basedn) + +def upg_check(response): + """Check that the user was assigned to the corresponding private group.""" + assert_equal(response['result']['uidnumber'], + response['result']['gidnumber']) + return True + +def not_upg_check(response): + """Check that the user was not assigned to the corresponding private group.""" + assert_not_equal(response['result']['uidnumber'], + response['result']['gidnumber']) + return True + +class test_user(Declarative): + + cleanup_commands = [ + ('user_del', [user1, user2, renameduser1, admin2], {'continue': True}), + ('group_del', [group1], {}), + ('automember_default_group_remove', [], {'type': u'group'}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent "%s"' % user1, + command=('user_show', [user1], {}), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Try to update non-existent "%s"' % user1, + command=('user_mod', [user1], dict(givenname=u'Foo')), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Try to delete non-existent "%s"' % user1, + command=('user_del', [user1], {}), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Try to rename non-existent "%s"' % user1, + command=('user_mod', [user1], dict(setattr=u'uid=%s' % renameduser1)), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Create "%s"' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = upg_check, + ), + + + dict( + desc='Try to create duplicate "%s"' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + expected=errors.DuplicateEntry( + message=u'user with name "%s" already exists' % user1), + ), + + + dict( + desc='Retrieve "%s"' % user1, + command=( + 'user_show', [user1], {} + ), + expected=dict( + result=dict( + dn=get_user_dn(user1), + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + value=user1, + summary=None, + ), + ), + + + dict( + desc='Search for "%s" with all=True' % user1, + command=( + 'user_find', [user1], {'all': True} + ), + expected=dict( + result=[ + { + 'dn': get_user_dn(user1), + 'cn': [u'Test User1'], + 'gecos': [u'Test User1'], + 'givenname': [u'Test'], + 'homedirectory': [u'/home/tuser1'], + 'krbprincipalname': [u'tuser1@' + api.env.realm], + 'loginshell': [u'/bin/sh'], + 'memberof_group': [u'ipausers'], + 'objectclass': objectclasses.user, + 'sn': [u'User1'], + 'uid': [user1], + 'uidnumber': [fuzzy_digits], + 'gidnumber': [fuzzy_digits], + 'ipauniqueid': [fuzzy_uuid], + 'mepmanagedentry': [get_group_dn(user1)], + 'krbpwdpolicyreference': [DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + 'nsaccountlock': False, + 'has_keytab': False, + 'has_password': False, + 'displayname': [u'Test User1'], + 'cn': [u'Test User1'], + 'initials': [u'TU'], + 'mail': [u'%s@%s' % (user1, api.env.domain)], + }, + ], + summary=u'1 user matched', + count=1, truncated=False, + ), + ), + + + dict( + desc='Search for "%s" with pkey-only=True' % user1, + command=( + 'user_find', [user1], {'pkey_only': True} + ), + expected=dict( + result=[ + { + 'dn': get_user_dn(user1), + 'uid': [user1], + }, + ], + summary=u'1 user matched', + count=1, truncated=False, + ), + ), + + + dict( + desc='Search for "%s" with minimal attributes' % user1, + command=( + 'user_find', [user1], {} + ), + expected=dict( + result=[ + dict( + dn=get_user_dn(user1), + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + nsaccountlock=False, + has_keytab=False, + has_password=False, + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + ), + ], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + + dict( + desc='Search for all users', + command=( + 'user_find', [], {} + ), + expected=dict( + result=[ + dict( + dn=get_user_dn(admin1), + homedirectory=[u'/home/admin'], + loginshell=[u'/bin/bash'], + sn=[u'Administrator'], + uid=[admin1], + nsaccountlock=False, + has_keytab=True, + has_password=True, + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + ), + dict( + dn=get_user_dn(user1), + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + nsaccountlock=False, + has_keytab=False, + has_password=False, + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + ), + ], + summary=u'2 users matched', + count=2, + truncated=False, + ), + ), + + + dict( + desc='Search for all users with a limit of 1', + command=( + 'user_find', [], dict(sizelimit=1,), + ), + expected=dict( + result=[ + dict( + dn=get_user_dn(admin1), + homedirectory=[u'/home/admin'], + loginshell=[u'/bin/bash'], + sn=[u'Administrator'], + uid=[admin1], + nsaccountlock=False, + has_keytab=True, + has_password=True, + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + ), + ], + summary=u'1 user matched', + count=1, + truncated=True, + ), + ), + + + dict( + desc='Disable "%s"' % user1, + command=( + 'user_disable', [user1], {} + ), + expected=dict( + result=True, + value=user1, + summary=u'Disabled user account "%s"' % user1, + ), + ), + + dict( + desc='Assert user is disabled', + command=('user_find', [user1], {}), + expected=dict( + result=[lambda d: d['nsaccountlock'] == True], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + dict( + desc='Enable "%s"' % user1, + command=( + 'user_enable', [user1], {} + ), + expected=dict( + result=True, + value=user1, + summary=u'Enabled user account "%s"' % user1, + ), + ), + + dict( + desc='Assert user "%s" is enabled' % user1, + command=('user_find', [user1], {}), + expected=dict( + result=[lambda d: d['nsaccountlock'] == False], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + dict( + desc='Disable "%s" using setattr' % user1, + command=('user_mod', [user1], dict(setattr=u'nsaccountlock=True')), + expected=dict( + result=lambda d: d['nsaccountlock'] == True, + value=user1, + summary=u'Modified user "%s"' % user1, + ), + ), + + dict( + desc='Enable "%s" using setattr' % user1, + command=('user_mod', [user1], dict(setattr=u'nsaccountlock=False')), + expected=dict( + result=lambda d: d['nsaccountlock'] == False, + value=user1, + summary=u'Modified user "%s"' % user1, + ), + ), + + dict( + desc='Disable "%s" using user_mod' % user1, + command=('user_mod', [user1], dict(nsaccountlock=True)), + expected=dict( + result=lambda d: d['nsaccountlock'] == True, + value=user1, + summary=u'Modified user "%s"' % user1, + ), + ), + + dict( + desc='Enable "%s" using user_mod' % user1, + command=('user_mod', [user1], dict(nsaccountlock=False)), + expected=dict( + result=lambda d: d['nsaccountlock'] == False, + value=user1, + summary=u'Modified user "%s"' % user1, + ), + ), + + dict( + desc='Try setting virtual attribute on "%s" using setattr' % user1, + command=('user_mod', [user1], dict(setattr=u'random=xyz123')), + expected=errors.ObjectclassViolation( + info='attribute "random" not allowed'), + ), + + dict( + desc='Update "%s"' % user1, + command=( + 'user_mod', [user1], dict(givenname=u'Finkle') + ), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "%s"' % user1, + value=user1, + ), + ), + + + dict( + desc='Try updating the krb ticket policy of "%s"' % user1, + command=( + 'user_mod', [user1], dict(setattr=u'krbmaxticketlife=88000') + ), + expected=errors.ObjectclassViolation( + info=u'attribute "krbmaxticketlife" not allowed'), + ), + + + dict( + desc='Retrieve "%s" to verify update' % user1, + command=('user_show', [user1], {}), + expected=dict( + result=dict( + dn=get_user_dn(user1), + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=None, + value=user1, + ), + + ), + + + dict( + desc='Rename "%s"' % user1, + command=('user_mod', [user1], dict(setattr=u'uid=%s' % renameduser1)), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[renameduser1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "%s"' % user1, + value=user1, + ), + ), + + + dict( + desc='Rename "%s" to same value' % renameduser1, + command=('user_mod', [renameduser1], dict(setattr=u'uid=%s' % renameduser1)), + expected=errors.EmptyModlist(), + ), + + + dict( + desc='Rename back "%s"' % renameduser1, + command=('user_mod', [renameduser1], dict(setattr=u'uid=%s' % user1)), + expected=dict( + result=dict( + givenname=[u'Finkle'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[u'ipausers'], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "%s"' % renameduser1, + value=renameduser1, + ), + ), + + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + + dict( + desc='Try to delete non-existent "%s"' % user1, + command=('user_del', [user1], {}), + expected=errors.NotFound(reason=u'tuser1: user not found'), + ), + + + dict( + desc='Create user "%s" with krb ticket policy' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + setattr=u'krbmaxticketlife=88000') + ), + expected=errors.ObjectclassViolation(info='attribute "krbmaxticketlife" not allowed'), + ), + + + dict( + desc='Create "%s" with SSH public key' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', ipasshpubkey=[sshpubkey]) + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + mail=[u'%s@%s' % (user1, api.env.domain)], + ipasshpubkey=[sshpubkey], + sshpubkeyfp=[sshpubkeyfp], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = upg_check, + ), + + + dict( + desc='Add an illegal SSH public key to "%r"' % user1, + command=('user_mod', [user1], dict(ipasshpubkey=[u"anal nathrach orth' bhais's bethad do che'l de'nmha"])), + expected=errors.ValidationError(name='sshpubkey', + error=u'invalid SSH public key'), + ), + + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + + dict( + desc='Create "%s"' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = upg_check, + ), + + + dict( + desc='Create "%s"' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2') + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user2)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + ), + ), + extra_check = upg_check, + ), + + + dict( + desc='Make non-existent "%s" the manager of "%s"' % (renameduser1, user2), + command=('user_mod', [user2], dict(manager=renameduser1)), + expected=errors.NotFound( + reason=u'manager %s not found' % renameduser1), + ), + + + dict( + desc='Make "%s" the manager of "%s"' % (user1, user2), + command=('user_mod', [user2], dict(manager=user1)), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + loginshell=[u'/bin/sh'], + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + memberof_group=[u'ipausers'], + mail=[u'%s@%s' % (user2, api.env.domain)], + nsaccountlock=False, + has_keytab=False, + has_password=False, + manager=[user1], + ), + summary=u'Modified user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Search for "%s" with manager "%s"' % (user2, user1), + command=( + 'user_find', [user2], {'manager': user1} + ), + expected=dict( + result=[ + dict( + dn=get_user_dn(user2), + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + loginshell=[u'/bin/sh'], + sn=[u'User2'], + uid=[user2], + nsaccountlock=False, + has_keytab=False, + has_password=False, + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user2, api.env.domain)], + manager=[user1], + ), + ], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + dict( + desc='Delete "%s" and "%s" at the same time' % (user1, user2), + command=('user_del', [user1, user2], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "tuser1,tuser2"', + value=u','.join((user1, user2)), + ), + ), + + dict( + desc='Try to retrieve non-existent "%s"' % user1, + command=('user_show', [user1], {}), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Try to update non-existent "%s"' % user1, + command=('user_mod', [user1], dict(givenname=u'Foo')), + expected=errors.NotFound(reason=u'%s: user not found' % user1), + ), + + + dict( + desc='Test an invalid login name "%s"' % invaliduser1, + command=('user_add', [invaliduser1], dict(givenname=u'Test', sn=u'User1')), + expected=errors.ValidationError(name='login', + error=u'may only include letters, numbers, _, -, . and $'), + ), + + + dict( + desc='Test a login name that is too long "%s"' % invaliduser2, + command=('user_add', [invaliduser2], + dict(givenname=u'Test', sn=u'User1')), + expected=errors.ValidationError(name='login', + error='can be at most 32 characters'), + ), + + + # The assumption on these next 4 tests is that if we don't get a + # validation error then the request was processed normally. + dict( + desc='Test that validation is disabled on deletes', + command=('user_del', [invaliduser1], {}), + expected=errors.NotFound( + reason=u'%s: user not found' % invaliduser1), + ), + + + dict( + desc='Test that validation is disabled on show', + command=('user_show', [invaliduser1], {}), + expected=errors.NotFound( + reason=u'%s: user not found' % invaliduser1), + ), + + + dict( + desc='Test that validation is disabled on find', + command=('user_find', [invaliduser1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 users matched', + result=[], + ), + ), + + + dict( + desc='Try to rename to invalid username "%s"' % user1, + command=('user_mod', [user1], dict(rename=invaliduser1)), + expected=errors.ValidationError(name='rename', + error=u'may only include letters, numbers, _, -, . and $'), + ), + + + dict( + desc='Try to rename to a username that is too long "%s"' % user1, + command=('user_mod', [user1], dict(rename=invaliduser2)), + expected=errors.ValidationError(name='login', + error='can be at most 32 characters'), + ), + + + dict( + desc='Create "%s"' % group1, + command=( + 'group_add', [group1], dict(description=u'Test desc') + ), + expected=dict( + value=group1, + summary=u'Added group "%s"' % group1, + result=dict( + cn=[group1], + description=[u'Test desc'], + gidnumber=[fuzzy_digits], + objectclass=objectclasses.group + [u'posixgroup'], + ipauniqueid=[fuzzy_uuid], + dn=get_group_dn(group1), + ), + ), + ), + + + dict( + desc='Try to user "%s" where the managed group exists' % group1, + command=( + 'user_add', [group1], dict(givenname=u'Test', sn=u'User1') + ), + expected=errors.ManagedGroupExistsError(group=group1) + ), + + + dict( + desc='Create "%s" with a full address' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + street=u'123 Maple Rd', l=u'Anytown', st=u'MD', + telephonenumber=u'410-555-1212', postalcode=u'01234-5678') + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + mail=[u'%s@%s' % (user1, api.env.domain)], + street=[u'123 Maple Rd'], + l=[u'Anytown'], + st=[u'MD'], + postalcode=[u'01234-5678'], + telephonenumber=[u'410-555-1212'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + ), + + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Create "%s" with random password' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', random=True) + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=True, + has_password=True, + randompassword=fuzzy_password, + krbextradata=[fuzzy_string], + krbpasswordexpiration=[fuzzy_dergeneralizedtime], + krblastpwdchange=[fuzzy_dergeneralizedtime], + dn=get_user_dn(user1), + ), + ), + ), + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Create "%s"' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2') + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user2)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + ), + ), + ), + + dict( + desc='Modify "%s" with random password' % user2, + command=( + 'user_mod', [user2], dict(random=True) + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + loginshell=[u'/bin/sh'], + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + memberof_group=[u'ipausers'], + mail=[u'%s@%s' % (user2, api.env.domain)], + nsaccountlock=False, + has_keytab=True, + has_password=True, + randompassword=fuzzy_password, + ), + summary=u'Modified user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Delete "%s"' % user2, + command=('user_del', [user2], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Create user "%s" with upper-case principal' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + krbprincipalname=user1.upper()) + ), + 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=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + ), + + + dict( + desc='Create user "%s" with bad realm in principal' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + krbprincipalname='%s@NOTFOUND.ORG' % user1) + ), + expected=errors.RealmMismatch() + ), + + + dict( + desc='Create user "%s" with malformed principal' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', + krbprincipalname='%s@BAD@NOTFOUND.ORG' % user1) + ), + expected=errors.MalformedUserPrincipal(principal='%s@BAD@NOTFOUND.ORG' % user1), + ), + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Change default home directory', + command=( + 'config_mod', [], dict(ipahomesrootdir=u'/other-home'), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Create user "%s" with different default home directory' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + homedirectory=[u'/other-home/tuser1'], + krbprincipalname=[u'tuser1@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + ), + + + dict( + desc='Reset default home directory', + command=( + 'config_mod', [], dict(ipahomesrootdir=u'/home'), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Change default login shell', + command=( + 'config_mod', [], dict(ipadefaultloginshell=u'/usr/bin/ipython'), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Create user "%s" with different default login shell' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1') + ), + 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'/usr/bin/ipython'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + initials=[u'TU'], + mail=[u'%s@%s' % (user1, api.env.domain)], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + ), + + dict( + desc='Reset default login shell', + command=( + 'config_mod', [], dict(ipadefaultloginshell=u'/bin/sh'), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Create "%s" without UPG' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', noprivate=True) + ), + expected=errors.NotFound(reason='Default group for new users is not POSIX'), + ), + + dict( + desc='Create "%s" without UPG with GID explicitly set' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2', noprivate=True, gidnumber=1000) + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + description=[], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[u'1000'], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + ), + ), + ), + + dict( + desc='Delete "%s"' % user2, + command=('user_del', [user2], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Change default user group', + command=( + 'config_mod', [], dict(ipadefaultprimarygroup=group1), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Create "%s" without UPG' % user1, + command=( + 'user_add', [user1], dict(givenname=u'Test', sn=u'User1', noprivate=True) + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[u'Test User1'], + givenname=[u'Test'], + description=[], + homedirectory=[u'/home/tuser1'], + krbprincipalname=[u'tuser1@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[group1], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = not_upg_check, + ), + + dict( + desc='Create "%s" without UPG with GID explicitly set' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2', noprivate=True, gidnumber=1000) + ), + expected=dict( + value=user2, + summary=u'Added user "%s"' % user2, + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + description=[], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[u'1000'], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[group1], + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + ), + ), + ), + + dict( + desc='Set %r as manager of %r' % (user1, user2), + command=( + 'user_mod', [user2], dict(manager=user1) + ), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + loginshell=[u'/bin/sh'], + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + memberof_group=[group1], + mail=[u'%s@%s' % (user2, api.env.domain)], + nsaccountlock=False, + has_keytab=False, + has_password=False, + manager=[user1], + ), + summary=u'Modified user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Rename "%s"' % user1, + command=('user_mod', [user1], dict(rename=renameduser1)), + expected=dict( + result=dict( + givenname=[u'Test'], + homedirectory=[u'/home/tuser1'], + loginshell=[u'/bin/sh'], + sn=[u'User1'], + uid=[renameduser1], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user1, api.env.domain)], + memberof_group=[group1], + nsaccountlock=False, + has_keytab=False, + has_password=False, + ), + summary=u'Modified user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Retrieve %r and check that manager is renamed' % user2, + command=( + 'user_show', [user2], {'all': True} + ), + expected=dict( + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[u'1000'], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[group1], + nsaccountlock=False, + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + manager=[renameduser1], + ), + value=user2, + summary=None, + ), + ), + + dict( + desc='Delete %r' % renameduser1, + command=('user_del', [renameduser1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % renameduser1, + value=renameduser1, + ), + ), + + dict( + desc='Retrieve %r and check that manager is gone' % user2, + command=( + 'user_show', [user2], {'all': True} + ), + expected=dict( + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user_base, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[u'1000'], + displayname=[u'Test User2'], + cn=[u'Test User2'], + mail=[u'%s@%s' % (user2, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + memberof_group=[group1], + nsaccountlock=False, + has_keytab=False, + has_password=False, + dn=get_user_dn(user2), + ), + value=user2, + summary=None, + ), + ), + + dict( + desc='Reset default user group', + command=( + 'config_mod', [], dict(ipadefaultprimarygroup=u'ipausers'), + ), + expected=lambda x, output: x is None, + ), + + dict( + desc='Try to remove the original admin user "%s"' % admin1, + command=('user_del', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + dict( + desc='Try to disable the original admin user "%s"' % admin1, + command=('user_disable', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + + dict( + desc='Create 2nd admin user "%s"' % admin2, + command=( + 'user_add', [admin2], dict(givenname=u'Second', sn=u'Admin') + ), + expected=dict( + value=admin2, + summary=u'Added user "%s"' % admin2, + result=dict( + gecos=[u'Second Admin'], + givenname=[u'Second'], + homedirectory=[u'/home/admin2'], + krbprincipalname=[u'admin2@' + api.env.realm], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'Admin'], + uid=[admin2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + displayname=[u'Second Admin'], + cn=[u'Second Admin'], + initials=[u'SA'], + mail=[u'%s@%s' % (admin2, api.env.domain)], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(admin2)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(admin2), + ), + ), + ), + + dict( + desc='Add "%s" to the admins group "%s"' % (admin2, admins_group), + command=('group_add_member', [admins_group], dict(user=admin2)), + expected=dict( + completed=1, + failed=dict( + member=dict( + group=tuple(), + user=tuple(), + ), + ), + result={ + 'dn': get_group_dn(admins_group), + 'member_user': [admin1, admin2], + 'gidnumber': [fuzzy_digits], + 'cn': [admins_group], + 'description': [u'Account administrators group'], + }, + ), + ), + + + dict( + desc='Retrieve admins group "%s" to verify membership is "%s","%s"' % (admins_group, admin1, admin2), + command=('group_show', [admins_group], {}), + expected=dict( + value=admins_group, + result=dict( + cn=[admins_group], + gidnumber=[fuzzy_digits], + description=[u'Account administrators group'], + dn=get_group_dn(admins_group), + member_user=[admin1, admin2], + ), + summary=None, + ), + ), + + dict( + desc='Disable 2nd admin user "%s", admins group "%s" should also contain enabled "%s"' % (admin2, admins_group, admin1), + command=( + 'user_disable', [admin2], {} + ), + expected=dict( + result=True, + value=admin2, + summary=u'Disabled user account "%s"' % admin2, + ), + ), + + dict( + desc='Assert 2nd admin user "%s" is disabled' % admin2, + command=('user_find', [admin2], {}), + expected=dict( + result=[lambda d: d['nsaccountlock'] == True], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + dict( + desc='Try to disable the origin admin user "%s"' % admin1, + command=('user_disable', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + dict( + desc='Try to remove the original admin user "%s"' % admin1, + command=('user_del', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + dict( + desc='Delete 2nd admin "%s"' % admin2, + command=('user_del', [admin2], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % admin2, + value=admin2, + ), + ), + + dict( + desc='Retrieve admins group "%s" to verify membership is "%s"' % (admins_group, admin1), + command=('group_show', [admins_group], {}), + expected=dict( + value=admins_group, + result=dict( + cn=[admins_group], + gidnumber=[fuzzy_digits], + description=[u'Account administrators group'], + dn=get_group_dn(admins_group), + member_user=[admin1], + ), + summary=None, + ), + ), + + dict( + desc='Assert original admin user "%s" is enabled' % admin1, + command=('user_find', [admin1], {}), + expected=dict( + result=[lambda d: d['nsaccountlock'] == False], + summary=u'1 user matched', + count=1, + truncated=False, + ), + ), + + dict( + desc='Try to remove the original admin user "%s"' % admin1, + command=('user_del', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + dict( + desc='Try to disable the original admin user "%s"' % admin1, + command=('user_disable', [admin1], {}), + expected=errors.LastMemberError(key=admin1, label=u'group', + container=admins_group), + ), + + dict( + desc='Set default automember group for groups as ipausers', + command=( + 'automember_default_group_set', [], dict( + type=u'group', + automemberdefaultgroup=u'ipausers' + ) + ), + expected=dict( + result=dict( + cn=[u'Group'], + automemberdefaultgroup=[DN(('cn', 'ipausers'), ('cn', 'groups'), ('cn', 'accounts'), api.env.basedn)], + ), + value=u'group', + summary=u'Set default (fallback) group for automember "group"', + ), + ), + + dict( + desc='Delete "%s"' % user2, + command=('user_del', [user2], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user2, + value=user2, + ), + ), + + dict( + desc='Create %r' % user2, + command=( + 'user_add', [user2], dict(givenname=u'Test', sn=u'User2') + ), + expected=dict( + value=user2, + summary=u'Added user "tuser2"', + result=dict( + gecos=[u'Test User2'], + givenname=[u'Test'], + homedirectory=[u'/home/tuser2'], + krbprincipalname=[u'tuser2@' + api.env.realm], + has_keytab=False, + has_password=False, + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User2'], + uid=[user2], + uidnumber=[fuzzy_digits], + gidnumber=[fuzzy_digits], + mail=[u'%s@%s' % (user2, api.env.domain)], + displayname=[u'Test User2'], + cn=[u'Test User2'], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), + api.env.basedn)], + mepmanagedentry=[DN(('cn', user2), ('cn', 'groups'), ('cn', 'accounts'), + api.env.basedn)], + memberof_group=[u'ipausers'], + dn=DN(('uid', 'tuser2'), ('cn', 'users'), ('cn', 'accounts'), + api.env.basedn), + ), + ), + ), + + dict( + desc='Create "%s" with UID 999' % user1, + command=( + 'user_add', [user1], dict( + givenname=u'Test', sn=u'User1', uidnumber=999) + ), + 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=[u'999'], + gidnumber=[u'999'], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = upg_check, + ), + + dict( + desc='Delete "%s"' % user1, + command=('user_del', [user1], {}), + expected=dict( + result=dict(failed=u''), + summary=u'Deleted user "%s"' % user1, + value=user1, + ), + ), + + dict( + desc='Create "%s" with old DNA_MAGIC uid 999' % user1, + command=( + 'user_add', [user1], dict( + givenname=u'Test', sn=u'User1', uidnumber=999, + version=u'2.49') + ), + 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=[lambda v: int(v) != 999], + gidnumber=[lambda v: int(v) != 999], + displayname=[u'Test User1'], + cn=[u'Test User1'], + mail=[u'%s@%s' % (user1, api.env.domain)], + initials=[u'TU'], + ipauniqueid=[fuzzy_uuid], + krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm), + ('cn','kerberos'),api.env.basedn)], + mepmanagedentry=[get_group_dn(user1)], + memberof_group=[u'ipausers'], + has_keytab=False, + has_password=False, + dn=get_user_dn(user1), + ), + ), + extra_check = upg_check, + ), + + ] diff --git a/ipatests/test_xmlrpc/xmlrpc_test.py b/ipatests/test_xmlrpc/xmlrpc_test.py new file mode 100644 index 000000000..bfe8efa46 --- /dev/null +++ b/ipatests/test_xmlrpc/xmlrpc_test.py @@ -0,0 +1,329 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Base class for all XML-RPC tests +""" + +import sys +import socket +import nose +from ipatests.util import assert_deepequal, Fuzzy +from ipalib import api, request, errors +from ipalib.x509 import valid_issuer +from ipapython.version import API_VERSION + + +# Matches a gidnumber like '1391016742' +# FIXME: Does it make more sense to return gidnumber, uidnumber, etc. as `int` +# or `long`? If not, we still need to return them as `unicode` instead of `str`. +fuzzy_digits = Fuzzy('^\d+$', type=basestring) + +uuid_re = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + +# Matches an ipauniqueid like u'784d85fd-eae7-11de-9d01-54520012478b' +fuzzy_uuid = Fuzzy('^%s$' % uuid_re) + +# Matches trusted domain GUID, like u'463bf2be-3456-4a57-979e-120304f2a0eb' +fuzzy_guid = fuzzy_uuid + +# Matches SID of a trusted domain +# SID syntax: http://msdn.microsoft.com/en-us/library/ff632068.aspx +_sid_identifier_authority = '(0x[0-9a-f]{1,12}|[0-9]{1,10})' +fuzzy_domain_sid = Fuzzy( + '^S-1-5-21-%(idauth)s-%(idauth)s-%(idauth)s$' % dict(idauth=_sid_identifier_authority) +) +fuzzy_user_or_group_sid = Fuzzy( + '^S-1-5-21-%(idauth)s-%(idauth)s-%(idauth)s-%(idauth)s$' % dict(idauth=_sid_identifier_authority) +) + +# Matches netgroup dn. Note (?i) at the beginning of the regexp is the ingnore case flag +fuzzy_netgroupdn = Fuzzy( + '(?i)ipauniqueid=%s,cn=ng,cn=alt,%s' % (uuid_re, api.env.basedn) +) + +# Matches sudocmd dn +fuzzy_sudocmddn = Fuzzy( + '(?i)ipauniqueid=%s,cn=sudocmds,cn=sudo,%s' % (uuid_re, api.env.basedn) +) + +# Matches a hash signature, not enforcing length +fuzzy_hash = Fuzzy('^([a-f0-9][a-f0-9]:)+[a-f0-9][a-f0-9]$', type=basestring) + +# Matches a date, like Tue Apr 26 17:45:35 2016 UTC +fuzzy_date = Fuzzy('^[a-zA-Z]{3} [a-zA-Z]{3} \d{2} \d{2}:\d{2}:\d{2} \d{4} UTC$') + +fuzzy_issuer = Fuzzy(type=basestring, test=lambda issuer: valid_issuer(issuer)) + +fuzzy_hex = Fuzzy('^0x[0-9a-fA-F]+$', type=basestring) + +# Matches password - password consists of all printable characters without whitespaces +# The only exception is space, but space cannot be at the beggingin or end of the pwd +fuzzy_password = Fuzzy('^\S([\S ]*\S)*$') + +# Matches generalized time value. Time format is: %Y%m%d%H%M%SZ +fuzzy_dergeneralizedtime = Fuzzy('^[0-9]{14}Z$') + +# match any string +fuzzy_string = Fuzzy(type=basestring) + +# case insensitive match of sets +def fuzzy_set_ci(s): + return Fuzzy(test=lambda other: set(x.lower() for x in other) == set(y.lower() for y in s)) + +try: + if not api.Backend.xmlclient.isconnected(): + api.Backend.xmlclient.connect(fallback=False) + res = api.Command['user_show'](u'notfound') +except errors.NetworkError: + server_available = False +except IOError: + server_available = False +except errors.NotFound: + server_available = True + + + +def assert_attr_equal(entry, key, value): + if type(entry) is not dict: + raise AssertionError( + 'assert_attr_equal: entry must be a %r; got a %r: %r' % ( + dict, type(entry), entry) + ) + if key not in entry: + raise AssertionError( + 'assert_attr_equal: entry has no key %r: %r' % (key, entry) + ) + if value not in entry[key]: + raise AssertionError( + 'assert_attr_equal: %r: %r not in %r' % (key, value, entry[key]) + ) + + +def assert_is_member(entry, value, key='member'): + if type(entry) is not dict: + raise AssertionError( + 'assert_is_member: entry must be a %r; got a %r: %r' % ( + dict, type(entry), entry) + ) + if key not in entry: + raise AssertionError( + 'assert_is_member: entry has no key %r: %r' % (key, entry) + ) + for member in entry[key]: + if member.startswith(value): + return + raise AssertionError( + 'assert_is_member: %r: %r not in %r' % (key, value, entry[key]) + ) + + +# Initialize the API. We do this here so that one can run the tests +# individually instead of at the top-level. If API.bootstrap() +# has already been called we continue gracefully. Other errors will be +# raised. + +class XMLRPC_test(object): + """ + Base class for all XML-RPC plugin tests + """ + + @classmethod + def setUpClass(cls): + if not server_available: + raise nose.SkipTest('%r: Server not available: %r' % + (cls.__module__, api.env.xmlrpc_uri)) + + def setUp(self): + if not api.Backend.xmlclient.isconnected(): + api.Backend.xmlclient.connect(fallback=False) + + def tearDown(self): + """ + nose tear-down fixture. + """ + request.destroy_context() + + def failsafe_add(self, obj, pk, **options): + """ + Delete possible leftover entry first, then add. + + This helps speed us up when a partial test failure has left LDAP in a + dirty state. + + :param obj: An Object like api.Object.user + :param pk: The primary key of the entry to be created + :param options: Kwargs to be passed to obj.add() + """ + try: + obj.methods['del'](pk) + except errors.NotFound: + pass + return obj.methods['add'](pk, **options) + + +IGNORE = """Command %r is missing attribute %r in output entry. + args = %r + options = %r + entry = %r""" + + +EXPECTED = """Expected %r to raise %s. + args = %r + options = %r + output = %r""" + + +UNEXPECTED = """Expected %r to raise %s, but caught different. + args = %r + options = %r + %s: %s""" + + +KWARGS = """Command %r raised %s with wrong kwargs. + args = %r + options = %r + kw_expected = %r + kw_got = %r""" + + +class Declarative(XMLRPC_test): + """A declarative-style test suite + + A Declarative test suite is controlled by the ``tests`` and + ``cleanup_commands`` class variables. + + The ``tests`` is a list of dictionaries with the following keys: + + ``desc`` + A name/description of the test + ``command`` + A (command, args, kwargs) triple specifying the command to run + ``expected`` + Can be either an ``errors.PublicError`` instance, in which case + the command must fail with the given error; or the + expected result. + The result is checked with ``tests.util.assert_deepequal``. + ``extra_check`` (optional) + A checking function that is called with the response. It must + return true for the test to pass. + + The ``cleanup_commands`` is a list of (command, args, kwargs) + triples. These are commands get run both before and after tests, + and must not fail. + """ + + cleanup_commands = tuple() + tests = tuple() + + def cleanup_generate(self, stage): + for (i, command) in enumerate(self.cleanup_commands): + func = lambda: self.cleanup(command) + func.description = '%s %s-cleanup[%d]: %r' % ( + self.__class__.__name__, stage, i, command + ) + yield (func,) + + def cleanup(self, command): + (cmd, args, options) = command + if cmd not in api.Command: + raise nose.SkipTest( + 'cleanup command %r not in api.Command' % cmd + ) + try: + api.Command[cmd](*args, **options) + except (errors.NotFound, errors.EmptyModlist): + pass + + def test_generator(self): + """ + Iterate through tests. + + nose reports each one as a seperate test. + """ + + # Iterate through pre-cleanup: + for tup in self.cleanup_generate('pre'): + yield tup + + # Iterate through the tests: + name = self.__class__.__name__ + for (i, test) in enumerate(self.tests): + nice = '%s[%d]: %s: %s' % ( + name, i, test['command'][0], test.get('desc', '') + ) + func = lambda: self.check(nice, **test) + func.description = nice + yield (func,) + + # Iterate through post-cleanup: + for tup in self.cleanup_generate('post'): + yield tup + + def check(self, nice, desc, command, expected, extra_check=None): + (cmd, args, options) = command + options.setdefault('version', API_VERSION) + if cmd not in api.Command: + raise nose.SkipTest('%r not in api.Command' % cmd) + if isinstance(expected, errors.PublicError): + self.check_exception(nice, cmd, args, options, expected) + elif hasattr(expected, '__call__'): + self.check_callable(nice, cmd, args, options, expected) + else: + self.check_output(nice, cmd, args, options, expected, extra_check) + + def check_exception(self, nice, cmd, args, options, expected): + klass = expected.__class__ + name = klass.__name__ + try: + output = api.Command[cmd](*args, **options) + except StandardError, e: + pass + else: + raise AssertionError( + EXPECTED % (cmd, name, args, options, output) + ) + if not isinstance(e, klass): + raise AssertionError( + UNEXPECTED % (cmd, name, args, options, e.__class__.__name__, e) + ) + # FIXME: the XML-RPC transport doesn't allow us to return structured + # information through the exception, so we can't test the kw on the + # client side. However, if we switch to using JSON-RPC for the default + # transport, the exception is a free-form data structure (dict). + # For now just compare the strings + assert_deepequal(expected.strerror, e.strerror) + + def check_callable(self, nice, cmd, args, options, expected): + output = dict() + e = None + try: + output = api.Command[cmd](*args, **options) + except StandardError, e: + pass + if not expected(e, output): + raise AssertionError( + UNEXPECTED % (cmd, args, options, e.__class__.__name__, e) + ) + + def check_output(self, nice, cmd, args, options, expected, extra_check): + got = api.Command[cmd](*args, **options) + assert_deepequal(expected, got, nice) + if extra_check and not extra_check(got): + raise AssertionError('Extra check %s failed' % extra_check) |