summaryrefslogtreecommitdiffstats
path: root/ipatests/test_xmlrpc
diff options
context:
space:
mode:
Diffstat (limited to 'ipatests/test_xmlrpc')
-rw-r--r--ipatests/test_xmlrpc/__init__.py22
-rw-r--r--ipatests/test_xmlrpc/objectclasses.py163
-rw-r--r--ipatests/test_xmlrpc/test_attr.py562
-rw-r--r--ipatests/test_xmlrpc/test_automember_plugin.py1095
-rw-r--r--ipatests/test_xmlrpc/test_automount_plugin.py582
-rw-r--r--ipatests/test_xmlrpc/test_baseldap_plugin.py159
-rw-r--r--ipatests/test_xmlrpc/test_batch_plugin.py232
-rw-r--r--ipatests/test_xmlrpc/test_cert_plugin.py454
-rw-r--r--ipatests/test_xmlrpc/test_config_plugin.py121
-rw-r--r--ipatests/test_xmlrpc/test_delegation_plugin.py300
-rw-r--r--ipatests/test_xmlrpc/test_dns_plugin.py1511
-rw-r--r--ipatests/test_xmlrpc/test_dns_realmdomains_integration.py168
-rw-r--r--ipatests/test_xmlrpc/test_external_members.py160
-rw-r--r--ipatests/test_xmlrpc/test_group_plugin.py1046
-rw-r--r--ipatests/test_xmlrpc/test_hbac_plugin.py497
-rw-r--r--ipatests/test_xmlrpc/test_hbacsvcgroup_plugin.py256
-rw-r--r--ipatests/test_xmlrpc/test_hbactest_plugin.py217
-rw-r--r--ipatests/test_xmlrpc/test_host_plugin.py939
-rw-r--r--ipatests/test_xmlrpc/test_hostgroup_plugin.py313
-rw-r--r--ipatests/test_xmlrpc/test_krbtpolicy.py150
-rw-r--r--ipatests/test_xmlrpc/test_nesting.py797
-rw-r--r--ipatests/test_xmlrpc/test_netgroup_plugin.py1362
-rw-r--r--ipatests/test_xmlrpc/test_passwd_plugin.py69
-rw-r--r--ipatests/test_xmlrpc/test_permission_plugin.py972
-rw-r--r--ipatests/test_xmlrpc/test_ping_plugin.py53
-rw-r--r--ipatests/test_xmlrpc/test_privilege_plugin.py412
-rw-r--r--ipatests/test_xmlrpc/test_pwpolicy_plugin.py244
-rw-r--r--ipatests/test_xmlrpc/test_range_plugin.py536
-rw-r--r--ipatests/test_xmlrpc/test_realmdomains_plugin.py164
-rw-r--r--ipatests/test_xmlrpc/test_replace.py236
-rw-r--r--ipatests/test_xmlrpc/test_role_plugin.py625
-rw-r--r--ipatests/test_xmlrpc/test_selfservice_plugin.py290
-rw-r--r--ipatests/test_xmlrpc/test_selinuxusermap_plugin.py934
-rw-r--r--ipatests/test_xmlrpc/test_service_plugin.py632
-rw-r--r--ipatests/test_xmlrpc/test_sudocmd_plugin.py327
-rw-r--r--ipatests/test_xmlrpc/test_sudocmdgroup_plugin.py693
-rw-r--r--ipatests/test_xmlrpc/test_sudorule_plugin.py781
-rw-r--r--ipatests/test_xmlrpc/test_trust_plugin.py159
-rw-r--r--ipatests/test_xmlrpc/test_user_plugin.py1837
-rw-r--r--ipatests/test_xmlrpc/xmlrpc_test.py329
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)