Fix group state for Windows

group no longer fails when domain is not specified
group.present now accepts group names without domains
local groups are assumed if domain is not specified
documentation improved
moved fix_local_user function to salt.utils.win_functions
now called get_sam_name
This commit is contained in:
twangboy 2017-08-21 18:39:09 -06:00
parent f9b4976c02
commit 4f4e34c79f
3 changed files with 190 additions and 53 deletions

View file

@ -12,6 +12,7 @@ from __future__ import absolute_import
# Import salt libs
import salt.utils
import salt.utils.win_functions
try:
@ -35,10 +36,18 @@ def __virtual__():
return (False, "Module win_groupadd: module only works on Windows systems")
def add(name, gid=None, system=False):
def add(name, **kwargs):
'''
Add the specified group
Args:
name (str):
The name of the group to add
Returns:
dict: A dictionary of results
CLI Example:
.. code-block:: bash
@ -57,21 +66,16 @@ def add(name, gid=None, system=False):
compObj = nt.GetObject('', 'WinNT://.,computer')
newGroup = compObj.Create('group', name)
newGroup.SetInfo()
ret['changes'].append((
'Successfully created group {0}'
).format(name))
ret['changes'].append('Successfully created group {0}'.format(name))
except pywintypes.com_error as com_err:
ret['result'] = False
if len(com_err.excepinfo) >= 2:
friendly_error = com_err.excepinfo[2].rstrip('\r\n')
ret['comment'] = (
'Failed to create group {0}. {1}'
).format(name, friendly_error)
ret['comment'] = 'Failed to create group {0}. {1}' \
''.format(name, friendly_error)
else:
ret['result'] = None
ret['comment'] = (
'The group {0} already exists.'
).format(name)
ret['comment'] = 'The group {0} already exists.'.format(name)
return ret
@ -80,6 +84,14 @@ def delete(name):
'''
Remove the named group
Args:
name (str):
The name of the group to remove
Returns:
dict: A dictionary of results
CLI Example:
.. code-block:: bash
@ -118,6 +130,14 @@ def info(name):
'''
Return information about a group
Args:
name (str):
The name of the group for which to get information
Returns:
dict: A dictionary of information about the group
CLI Example:
.. code-block:: bash
@ -151,6 +171,17 @@ def getent(refresh=False):
'''
Return info on all groups
Args:
refresh (bool):
Refresh the info for all groups in ``__context__``. If False only
the groups in ``__context__`` wil be returned. If True the
``__context__`` will be refreshed with current data and returned.
Default is False
Returns:
A list of groups and their information
CLI Example:
.. code-block:: bash
@ -184,14 +215,24 @@ def getent(refresh=False):
def adduser(name, username):
'''
add a user to a group
Add a user to a group
Args:
name (str):
The name of the group to modify
username (str):
The name of the user to add to the group
Returns:
dict: A dictionary of results
CLI Example:
.. code-block:: bash
salt '*' group.adduser foo username
'''
ret = {'name': name,
@ -209,7 +250,7 @@ def adduser(name, username):
'/', '\\').encode('ascii', 'backslashreplace').lower())
try:
if __fixlocaluser(username.lower()) not in existingMembers:
if salt.utils.win_functions.get_sam_name(username) not in existingMembers:
if not __opts__['test']:
groupObj.Add('WinNT://' + username.replace('\\', '/'))
@ -233,14 +274,24 @@ def adduser(name, username):
def deluser(name, username):
'''
remove a user from a group
Remove a user from a group
Args:
name (str):
The name of the group to modify
username (str):
The name of the user to remove from the group
Returns:
dict: A dictionary of results
CLI Example:
.. code-block:: bash
salt '*' group.deluser foo username
'''
ret = {'name': name,
@ -258,7 +309,7 @@ def deluser(name, username):
'/', '\\').encode('ascii', 'backslashreplace').lower())
try:
if __fixlocaluser(username.lower()) in existingMembers:
if salt.utils.win_functions.get_sam_name(username) in existingMembers:
if not __opts__['test']:
groupObj.Remove('WinNT://' + username.replace('\\', '/'))
@ -282,14 +333,25 @@ def deluser(name, username):
def members(name, members_list):
'''
remove a user from a group
Ensure a group contains only the members in the list
Args:
name (str):
The name of the group to modify
members_list (str):
A single user or a comma separated list of users. The group will
contain only the users specified in this list.
Returns:
dict: A dictionary of results
CLI Example:
.. code-block:: bash
salt '*' group.members foo 'user1,user2,user3'
'''
ret = {'name': name,
@ -297,7 +359,7 @@ def members(name, members_list):
'changes': {'Users Added': [], 'Users Removed': []},
'comment': []}
members_list = [__fixlocaluser(thisMember) for thisMember in members_list.lower().split(",")]
members_list = [salt.utils.win_functions.get_sam_name(m) for m in members_list.split(",")]
if not isinstance(members_list, list):
ret['result'] = False
ret['comment'].append('Members is not a list object')
@ -364,27 +426,26 @@ def members(name, members_list):
return ret
def __fixlocaluser(username):
'''
prefixes a username w/o a backslash with the computername
i.e. __fixlocaluser('Administrator') would return 'computername\administrator'
'''
if '\\' not in username:
username = ('{0}\\{1}').format(__salt__['grains.get']('host'), username)
return username.lower()
def list_groups(refresh=False):
'''
Return a list of groups
Args:
refresh (bool):
Refresh the info for all groups in ``__context__``. If False only
the groups in ``__context__`` wil be returned. If True, the
``__context__`` will be refreshed with current data and returned.
Default is False
Returns:
list: A list of groups on the machine
CLI Example:
.. code-block:: bash
salt '*' group.getent
salt '*' group.list_groups
'''
if 'group.list_groups' in __context__ and not refresh:
return __context__['group.getent']

View file

@ -3,8 +3,13 @@
Management of user groups
=========================
The group module is used to create and manage unix group settings, groups
can be either present or absent:
The group module is used to create and manage group settings, groups can be
either present or absent. User/Group names can be passed to the ``adduser``,
``deluser``, and ``members`` parameters. ``adduser`` and ``deluser`` can be used
together but not with ``members``.
In Windows, if no domain is specified in the user or group name (ie:
`DOMAIN\username``) the module will assume a local user or group.
.. code-block:: yaml
@ -36,6 +41,10 @@ import sys
# Import 3rd-party libs
import salt.ext.six as six
# Import Salt libs
import salt.utils
import salt.utils.win_functions
def _changes(name,
gid=None,
@ -50,6 +59,18 @@ def _changes(name,
if not lgrp:
return False
# User and Domain names are not case sensitive in Windows. Let's make them
# all lower case so we can compare properly
if salt.utils.is_windows():
if lgrp['members']:
lgrp['members'] = [user.lower() for user in lgrp['members']]
if members:
members = [salt.utils.win_functions.get_sam_name(user) for user in members]
if addusers:
addusers = [salt.utils.win_functions.get_sam_name(user) for user in addusers]
if delusers:
delusers = [salt.utils.win_functions.get_sam_name(user) for user in delusers]
change = {}
if gid:
if lgrp['gid'] != gid:
@ -57,7 +78,7 @@ def _changes(name,
if members:
# -- if new member list if different than the current
if set(lgrp['members']) ^ set(members):
if set(lgrp['members']).symmetric_difference(members):
change['members'] = members
if addusers:
@ -82,28 +103,55 @@ def present(name,
'''
Ensure that a group is present
name
The name of the group to manage
Args:
gid
The group id to assign to the named group; if left empty, then the next
available group id will be assigned
name (str):
The name of the group to manage
system
Whether or not the named group is a system group. This is essentially
the '-r' option of 'groupadd'.
gid (str):
The group id to assign to the named group; if left empty, then the
next available group id will be assigned. Ignored on Windows
addusers
List of additional users to be added as a group members.
system (bool):
Whether or not the named group is a system group. This is essentially
the '-r' option of 'groupadd'. Ignored on Windows
delusers
Ensure these user are removed from the group membership.
addusers (list):
List of additional users to be added as a group members. Cannot
conflict with names in delusers. Cannot be used in conjunction with
members.
members
Replace existing group members with a list of new members.
delusers (list):
Ensure these user are removed from the group membership. Cannot
conflict with names in addusers. Cannot be used in conjunction with
members.
Note: Options 'members' and 'addusers/delusers' are mutually exclusive and
can not be used together.
members (list):
Replace existing group members with a list of new members. Cannot be
used in conjunction with addusers or delusers.
Example:
.. code-block:: yaml
# Adds DOMAIN\db_admins and Administrators to the local db_admin group
# Removes Users
db_admin:
group.present:
- addusers:
- DOMAIN\db_admins
- Administrators
- delusers:
- Users
# Ensures only DOMAIN\domain_admins and the local Administrator are
# members of the local Administrators group. All other users are
# removed
Administrators:
group.present:
- members:
- DOMAIN\domain_admins
- Administrator
'''
ret = {'name': name,
'changes': {},
@ -233,8 +281,17 @@ def absent(name):
'''
Ensure that the named group is absent
name
The name of the group to remove
Args:
name (str):
The name of the group to remove
Example:
.. code-block:: yaml
# Removes the local group `db_admin`
db_admin:
group.absent
'''
ret = {'name': name,
'changes': {},

View file

@ -4,6 +4,9 @@ Various functions to be used by windows during start up and to monkey patch
missing functions in other modules
'''
from __future__ import absolute_import
import platform
# Import Salt Libs
from salt.exceptions import CommandExecutionError
# Import 3rd Party Libs
@ -138,3 +141,19 @@ def get_current_user():
return False
return user_name
def get_sam_name(username):
'''
Gets the SAM name for a user. It basically prefixes a username without a
backslash with the computer name. If the username contains a backslash, it
is returned as is.
Everything is returned lower case
i.e. salt.utils.fix_local_user('Administrator') would return 'computername\administrator'
'''
if '\\' not in username:
username = '{0}\\{1}'.format(platform.node(), username)
return username.lower()