# Authors: # Rob Crittenden # Pavel Zuna # # 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 . """ Test the `ipalib/plugins/group.py` module. """ import functools import pytest from ipalib import api, errors from ipatests.test_xmlrpc import objectclasses from xmlrpc_test import (Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_set_ci, add_sid, add_oc, XMLRPC_test, raises_exact) from ipapython.dn import DN from ipatests.test_xmlrpc.test_user_plugin import get_user_result from ipatests.test_xmlrpc.ldaptracker import Tracker from ipatests.test_xmlrpc.test_user_plugin import UserTracker from ipatests.util import assert_deepequal 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=[ add_sid({ 'dn': get_group_dn('admins'), 'member_user': [u'admin'], 'gidnumber': [fuzzy_digits], 'cn': [u'admins'], 'description': [u'Account administrators group'], 'objectclass': fuzzy_set_ci(add_oc( objectclasses.posixgroup, u'ipantgroupattrs')), 'ipauniqueid': [fuzzy_uuid], }), add_sid({ 'dn': get_group_dn('editors'), 'gidnumber': [fuzzy_digits], 'cn': [u'editors'], 'description': [u'Limited admins who can edit other users'], 'objectclass': fuzzy_set_ci(add_oc( objectclasses.posixgroup, u'ipantgroupattrs', check_sidgen=True)), 'ipauniqueid': [fuzzy_uuid], }, check_sidgen=True), 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], ), add_sid(dict( dn=get_group_dn(group2), cn=[group2], description=[u'New desc 2'], gidnumber=[fuzzy_digits], objectclass=fuzzy_set_ci(add_oc( objectclasses.posixgroup, u'ipantgroupattrs')), 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 or 'failed' in output), ), dict( desc='Remove group %r with external membership' % (group3), command=('group_del', [group3], {}), expected=dict( result=dict(failed=[]), 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=[]), 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=[]), 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=get_user_result(user1, u'Test', u'User1', 'add'), ), ), 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=[]), 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=[]), 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=get_user_result( user1, u'Test', u'User1', 'add', description=[], objectclass=add_oc(objectclasses.user_base, u'ipantuserattrs'), gidnumber=[u'1000'], omit=['mepmanagedentry'], ), ), ), 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=[]), summary=u'Deleted user "%s"' % user1, value=[user1], ), ), ] class test_group_remove_group_from_protected_group(Declarative): cleanup_commands = [ ('group_del', [group1], {}), ] tests = [ # Test scenario from ticket #4448 # https://fedorahosted.org/freeipa/ticket/4448 dict( desc='Add group %s' % 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'], objectclass=objectclasses.posixgroup, gidnumber=[fuzzy_digits], ipauniqueid=[fuzzy_uuid], dn=get_group_dn(group1), ), ), ), dict( desc='Add %s group to admins group' % group1, command=('group_add_member', [u'admins'], dict(group=group1)), expected=dict( completed=1, failed=dict( member=dict( group=tuple(), user=tuple(), ), ), result=dict( dn=get_group_dn('admins'), member_user=[u'admin'], member_group=[group1], gidnumber=[fuzzy_digits], cn=[u'admins'], description=[u'Account administrators group'], ), ), ), dict( desc='Remove %s group from admins group' % group1, command=('group_remove_member', [u'admins'], dict(group=group1)), expected=dict( completed=1, failed=dict( member=dict( group=tuple(), user=tuple(), ), ), result=dict( dn=get_group_dn(u'admins'), cn=[u'admins'], gidnumber=[fuzzy_digits], member_user=[u'admin'], description=[u'Account administrators group'], ), ), ), ] class test_group_full_set_of_objectclass_not_available_post_detach(Declarative): # https://fedorahosted.org/freeipa/ticket/4909#comment:1 cleanup_commands = [ ('group_del', [user1], {}), ('user_del', [user1], {}), ] 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=get_user_result(user1, u'Test', u'User1', 'add'), ), ), 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='Show group - check objectclass', command=('group_show', [user1], dict(all=True)), expected=dict( value=user1, result={ 'cn':[user1], 'description': [u'User private group for tuser1'], 'gidnumber': [fuzzy_digits], 'dn': get_group_dn('tuser1'), 'ipauniqueid': [fuzzy_uuid], 'objectclass': objectclasses.posixgroup, }, summary=None, ), ), dict( desc='Add member back to the detached group', command=('group_add_member', [user1], dict(user=user1)), expected=dict( completed=1, failed=dict( member=dict( group=tuple(), user=tuple(), ), ), result={ 'dn': get_group_dn('tuser1'), 'member_user': [user1], 'gidnumber': [fuzzy_digits], 'cn': [user1], 'description': [u'User private group for tuser1'], }, ), ), ] class GroupTracker(Tracker): """ Class for host plugin like tests """ retrieve_keys = {u'dn', u'cn', u'gidnumber', u'member_user', u'member_group'} retrieve_all_keys = retrieve_keys | {u'ipauniqueid', u'objectclass'} create_keys = retrieve_all_keys update_keys = retrieve_keys - {u'dn'} add_member_keys = retrieve_keys | {u'description'} def __init__(self, name): super(GroupTracker, self).__init__(default_version=None) self.cn = name self.dn = get_group_dn(name) def make_create_command(self, nonposix=False, external=False, force=True): """ Make function that creates a group using 'group-add' """ return self.make_command('group_add', self.cn, nonposix=nonposix, external=external) def make_delete_command(self): """ Make function that deletes a group using 'group-del' """ return self.make_command('group_del', self.cn) def make_retrieve_command(self, all=False, raw=False): """ Make function that retrieves a group using 'group-show' """ return self.make_command('group_show', self.cn, all=all) def make_find_command(self, *args, **kwargs): """ Make function that searches for a group using 'group-find' """ return self.make_command('group_find', *args, **kwargs) def make_update_command(self, updates): """ Make function that updates a group using 'group-mod' """ return self.make_command('group_mod', self.cn, **updates) def make_add_member_command(self, options={}): """ Make function that adds a member to a group Attention: only works for one user OR group! """ if u'user' in options: self.attrs[u'member_user'] = [options[u'user']] elif u'group' in options: self.attrs[u'member_group'] = [options[u'group']] self.adds = options return self.make_command('group_add_member', self.cn, **options) def make_remove_member_command(self, options={}): """ Make function that removes a member from a group Attention: only works for one user OR group! """ if u'user' in options: del self.attrs[u'member_user'] elif u'group' in options: del self.attrs[u'member_group'] return self.make_command('group_remove_member', self.cn, **options) def make_detach_command(self): """ Make function that detaches a managed group using 'group-detach' """ self.exists = True return self.make_command('group_detach', self.cn) def track_create(self): """ Updates expected state for group creation""" self.attrs = dict( dn=get_group_dn(self.cn), cn=[self.cn], gidnumber=[fuzzy_digits], ipauniqueid=[fuzzy_uuid], objectclass=objectclasses.posixgroup, ) self.exists = True def check_create(self, result): """ Checks 'group_add' command result """ assert_deepequal(dict( value=self.cn, summary=u'Added group "%s"' % self.cn, result=self.filter_attrs(self.create_keys) ), result) def check_delete(self, result): """ Checks 'group_del' command result """ assert_deepequal(dict( value=[self.cn], summary=u'Deleted group "%s"' % self.cn, result=dict(failed=[]), ), result) def check_retrieve(self, result, all=False, raw=False): """ Checks 'group_show' command result """ if all: expected = self.filter_attrs(self.retrieve_all_keys) else: expected = self.filter_attrs(self.retrieve_keys) assert_deepequal(dict( value=self.cn, summary=None, result=expected ), result) def check_find(self, result, all=False, raw=False): """ Checks 'group_find' command result """ if all: expected = self.filter_attrs(self.retrieve_all_keys) else: expected = self.filter_attrs(self.retrieve_keys) assert_deepequal(dict( count=1, truncated=False, summary=u'1 group matched', result=[expected], ), result) def check_update(self, result, extra_keys={}): """ Checks 'group_mod' command result """ assert_deepequal(dict( value=self.cn, summary=u'Modified group "%s"' % self.cn, result=self.filter_attrs(self.update_keys | set(extra_keys)) ), result) def check_add_member(self, result): """ Checks 'group_add_member' command result """ assert_deepequal(dict( completed=1, failed={u'member': {u'group': (), u'user': ()}}, result=self.filter_attrs(self.add_member_keys) ), result) def check_add_member_negative(self, result): """ Checks 'group_add_member' command result when expected result is failure of the operation""" if u'member_user' in self.attrs: del self.attrs[u'member_user'] elif u'member_group' in self.attrs: del self.attrs[u'member_group'] expected = dict( completed=0, failed={u'member': {u'group': (), u'user': ()}}, result=self.filter_attrs(self.add_member_keys) ) if u'user' in self.adds: expected[u'failed'][u'member'][u'user'] = [( self.adds[u'user'], u'no such entry')] elif u'group' in self.adds: expected[u'failed'][u'member'][u'group'] = [( self.adds[u'group'], u'no such entry')] assert_deepequal(expected, result) def check_remove_member(self, result): """ Checks 'group_remove_member' command result """ assert_deepequal(dict( completed=1, failed={u'member': {u'group': (), u'user': ()}}, result=self.filter_attrs(self.add_member_keys) ), result) def check_detach(self, result): """ Checks 'group_detach' command result """ assert_deepequal(dict( value=self.cn, summary=u'Detached group "%s" from user "%s"' % ( self.cn, self.cn), result=True ), result) def make_fixture_detach(self, request): """Make a pytest fixture for this tracker The fixture ensures the plugin entry does not exist before and after the tests that use itself. """ def cleanup(): pass request.addfinalizer(cleanup) return self