Change how members are retrieved in win_groupadd

This is a solution that works for the other functions that interact with
group objects, making it a better overall solution to be used widely
in win_groupadd.

This also updates the mocking in the tests such that we use a more
comprehensive fake class, and more intelligent mocking of get_sam_name
using a lambda.
This commit is contained in:
Erik Johnson 2017-10-23 13:11:46 -05:00 committed by twangboy
parent 6ab82394be
commit 5ce14df82c
No known key found for this signature in database
GPG Key ID: 93FF3BDEB278C9EB
2 changed files with 98 additions and 99 deletions

View File

@ -42,6 +42,20 @@ def _get_computer_object():
return nt.GetObject('', 'WinNT://.,computer')
def _get_group_object(name):
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://./' + name + ',group')
def _get_username(member):
'''
Resolve the username from the member object returned from a group query
'''
return member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower()
def add(name, **kwargs):
'''
Add the specified group
@ -147,14 +161,9 @@ def info(name):
salt '*' group.info foo
'''
groupObj = _get_group_object(name)
existing_members = _get_group_members(groupObj)
try:
gr_name = groupObj.Name
gr_mem = []
for member in existing_members:
gr_mem.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace'))
gr_mem = [_get_username(x) for x in groupObj.members()]
except pywintypes.com_error:
return False
@ -199,13 +208,8 @@ def getent(refresh=False):
results = nt.GetObject('', 'WinNT://.')
results.Filter = ['group']
for result in results:
member_list = []
for member in result.members():
member_list.append(
member.AdsPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace'))
group = {'gid': __salt__['file.group_to_gid'](result.name),
'members': member_list,
'members': [_get_username(x) for x in result.members()],
'name': result.name,
'passwd': 'x'}
ret.append(group)
@ -213,21 +217,6 @@ def getent(refresh=False):
return ret
def _get_group_object(name):
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
return nt.GetObject('', 'WinNT://./' + name + ',group')
def _get_group_members(groupObj):
existingMembers = []
for member in groupObj.members():
existingMembers.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower())
return existingMembers
def adduser(name, username, **kwargs):
'''
Add a user to a group
@ -256,10 +245,11 @@ def adduser(name, username, **kwargs):
'comment': ''}
groupObj = _get_group_object(name)
existingMembers = _get_group_members(groupObj)
existingMembers = [_get_username(x) for x in groupObj.members()]
username = salt.utils.win_functions.get_sam_name(username)
try:
if salt.utils.win_functions.get_sam_name(username) not in existingMembers:
if username not in existingMembers:
if not __opts__['test']:
groupObj.Add('WinNT://' + username.replace('\\', '/'))
@ -309,7 +299,7 @@ def deluser(name, username, **kwargs):
'comment': ''}
groupObj = _get_group_object(name)
existingMembers = _get_group_members(groupObj)
existingMembers = [_get_username(x) for x in groupObj.members()]
try:
if salt.utils.win_functions.get_sam_name(username) in existingMembers:
@ -368,10 +358,8 @@ def members(name, members_list, **kwargs):
ret['comment'].append('Members is not a list object')
return ret
pythoncom.CoInitialize()
nt = win32com.client.Dispatch('AdsNameSpaces')
try:
groupObj = nt.GetObject('', 'WinNT://./' + name + ',group')
groupObj = _get_group_object(name)
except pywintypes.com_error as com_err:
if len(com_err.excepinfo) >= 2:
friendly_error = com_err.excepinfo[2].rstrip('\r\n')
@ -380,12 +368,7 @@ def members(name, members_list, **kwargs):
'Failure accessing group {0}. {1}'
).format(name, friendly_error))
return ret
existingMembers = []
for member in groupObj.members():
existingMembers.append(
member.ADSPath.replace('WinNT://', '').replace(
'/', '\\').encode('ascii', 'backslashreplace').lower())
existingMembers = [_get_username(x) for x in groupObj.members()]
existingMembers.sort()
members_list.sort()

View File

@ -19,6 +19,7 @@ from tests.support.mock import (
# Import Salt Libs
import salt.modules.win_groupadd as win_groupadd
import salt.utils.win_functions
# Import Other Libs
# pylint: disable=unused-import
@ -32,6 +33,37 @@ except ImportError:
# pylint: enable=unused-import
class MockMember(object):
def __init__(self, name):
self.ADSPath = name
class MockGroupObj(object):
def __init__(self, ads_users):
self._members = [MockMember(x) for x in ads_users]
def members(self):
return self._members
def Add(self):
'''
This should be a no-op unless we want to test raising an error, in
which case this should be overridden in a subclass.
'''
pass
def Remove(self):
'''
This should be a no-op unless we want to test raising an error, in
which case this should be overridden in a subclass.
'''
pass
if not NO_MOCK:
sam_mock = MagicMock(side_effect=lambda x: 'HOST\\' + x)
@skipIf(not HAS_WIN_LIBS, 'win_groupadd unit tests can only be run if win32com, pythoncom, and pywintypes are installed')
@skipIf(NO_MOCK, NO_MOCK_REASON)
class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
@ -151,21 +183,14 @@ class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
'''
Test if it return information about a group.
'''
members = MagicMock(return_value=['HOST\\steve'])
with patch.object(win_groupadd, '_get_group_object', Mock()), \
patch.object(win_groupadd, '_get_group_members', members):
groupobj_mock = MagicMock(return_value=MockGroupObj(['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock):
self.assertDictEqual(win_groupadd.info('salt'),
{'gid': None,
'members': ['user1'],
'members': ['HOST\\steve'],
'passwd': None,
'name': 'salt'})
with patch(win_groupadd.win32.client, 'flag', 1):
self.assertFalse(win_groupadd.info('dc=salt'))
with patch(win_groupadd.win32.client, 'flag', 2):
self.assertFalse(win_groupadd.info('dc=salt'))
# 'getent' function tests: 1
def test_getent(self):
@ -175,73 +200,70 @@ class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(win_groupadd.__context__, {'group.getent': True}):
self.assertTrue(win_groupadd.getent())
# 'adduser' function tests: 1
# 'adduser' function tests: 3
def test_adduser(self):
'''
Test adding a user to a group
'''
members = MagicMock(return_value=['HOST\\steve'])
with patch.object(win_groupadd, '_get_group_object', Mock()),\
patch.object(win_groupadd, '_get_group_members', members),\
patch('salt.utils.win_functions.get_sam_name', return_value='HOST\\spongebob'):
groupobj_mock = MagicMock(return_value=MockGroupObj(['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'spongebob'),
{'changes': {'Users Added': ['spongebob']},
{'changes': {'Users Added': ['HOST\\spongebob']},
'comment': '',
'name': 'foo',
'result': True})
def test_add_user_already_exists(self):
def test_adduser_already_exists(self):
'''
Test adding a user that already exists
'''
members = MagicMock(return_value=['HOST\\steve'])
with patch.object(win_groupadd, '_get_group_object', Mock()), \
patch.object(win_groupadd, '_get_group_members', members), \
patch('salt.utils.win_functions.get_sam_name', return_value='HOST\\spongebob'):
groupobj_mock = MagicMock(return_value=MockGroupObj(['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'spongebob'),
{'changes': {'Users Added': ['spongebob']},
'comment': '',
win_groupadd.adduser('foo', 'HOST\\steve'),
{'changes': {'Users Added': []},
'comment': 'User HOST\\steve is already a member of foo',
'name': 'foo',
'result': True})
'result': None})
def test_add_user_error(self):
def test_adduser_error(self):
'''
Test adding a user and encountering an error
'''
# Create mock group object
class GroupObj(object):
# Create mock group object with mocked Add function which raises the
# exception we need in order to test the error case.
class GroupObj(MockGroupObj):
def Add(self, name):
raise pywintypes.com_error(-2147352567, 'Exception occurred.', (0, None, 'C', None, 0, -2146788248), None)
groupobj_mock = MagicMock(return_value=GroupObj())
members = MagicMock(return_value=['HOST\\steve'])
with patch.object(win_groupadd, '_get_group_object', groupobj_mock):
with patch.object(win_groupadd, '_get_group_members', members):
comt = ('Failed to add username to group foo. C')
self.assertDictEqual(
win_groupadd.adduser('foo', 'username'),
{'changes': {'Users Added': []},
'name': 'foo',
'comment': comt,
'result': False})
groupobj_mock = MagicMock(return_value=GroupObj(['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.adduser('foo', 'username'),
{'changes': {'Users Added': []},
'name': 'foo',
'comment': 'Failed to add HOST\\username to group foo. C',
'result': False})
# 'deluser' function tests: 1
# 'deluser' function tests: 3
def test_deluser(self):
'''
Test removing a user from a group
'''
# Test removing a user
members = MagicMock(return_value=['HOST\\spongebob'])
with patch.object(win_groupadd, '_get_group_object', Mock()), \
patch.object(win_groupadd, '_get_group_members', members), \
patch('salt.utils.win_functions.get_sam_name', return_value='HOST\\spongebob'):
groupobj_mock = MagicMock(return_value=MockGroupObj(['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
ret = {'changes': {'Users Removed': ['spongebob']},
'comment': '',
'name': 'foo', 'result': True}
'name': 'foo',
'result': True}
self.assertDictEqual(win_groupadd.deluser('foo', 'spongebob'), ret)
def test_deluser_no_user(self):
@ -249,15 +271,13 @@ class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
Test removing a user from a group and that user is not a member of the
group
'''
# Test removing a user that's not in the group
members = MagicMock(return_value=['HOST\\steve'])
with patch.object(win_groupadd, '_get_group_object', Mock()), \
patch.object(win_groupadd, '_get_group_members', members), \
patch('salt.utils.win_functions.get_sam_name', return_value='HOST\\spongebob'):
groupobj_mock = MagicMock(return_value=MockGroupObj(['WinNT://HOST/steve']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
ret = {'changes': {'Users Removed': []},
'comment': 'User username is not a member of foo',
'name': 'foo', 'result': None}
'comment': 'User HOST\\spongebob is not a member of foo',
'name': 'foo',
'result': None}
self.assertDictEqual(win_groupadd.deluser('foo', 'username'), ret)
def test_deluser_error(self):
@ -268,18 +288,14 @@ class WinGroupTestCase(TestCase, LoaderModuleMockMixin):
def Remove(self, name):
raise pywintypes.com_error(-2147352567, 'Exception occurred.', (0, None, 'C', None, 0, -2146788248), None)
groupobj_mock = MagicMock(return_value=GroupObj())
members = MagicMock(return_value=['HOST\\spongebob'])
groupobj_mock = MagicMock(return_value=GroupObj(['WinNT://HOST/spongebob']))
with patch.object(win_groupadd, '_get_group_object', groupobj_mock), \
patch.object(win_groupadd, '_get_group_members', members), \
patch('salt.utils.win_functions.get_sam_name', return_value='HOST\\spongebob'):
comt = ('Failed to remove spongebob from group foo. C')
patch.object(salt.utils.win_functions, 'get_sam_name', sam_mock):
self.assertDictEqual(
win_groupadd.deluser('foo', 'spongebob'),
{'changes': {'Users Removed': []},
'name': 'foo',
'comment': comt,
'comment': 'Failed to remove spongebob from group foo. C',
'result': False})
# 'members' function tests: 1