<commit_before># -*- coding: utf-8 -*-
'''
Management of user accounts
===========================

The user module is used to create and manage user settings, users can be set
as either absent or present

.. code-block:: yaml

    fred:
      user.present:
        - fullname: <NAME>
        - shell: /bin/zsh
        - home: /home/fred
        - uid: 4000
        - gid: 4000
        - groups:
          - wheel
          - storage
          - games

    testuser:
      user.absent
'''

# Import python libs
import logging
import os

# Import salt libs
import salt.utils

log = logging.getLogger(__name__)


def _group_changes(cur, wanted, remove=False):
    '''
    Determine if the groups need to be changed
    '''
    old = set(cur)
    new = set(wanted)
    if (remove and old != new) or (not remove and not new.issubset(old)):
        return True
    return False


def _changes(name,
             uid=None,
             gid=None,
             groups=None,
             optional_groups=None,
             remove_groups=True,
             home=None,
             createhome=True,
             password=<PASSWORD>,
             enforce_password=True,
             empty_password=False,
             shell=None,
             fullname='',
             roomnumber='',
             workphone='',
             homephone='',
             date=0,
             mindays=0,
             maxdays=999999,
             inactdays=0,
             warndays=7,
             expire=-1):
    '''
    Return a dict of the changes required for a user if the user is present,
    otherwise return False.

    Updated in 2014.7.0 to include support for shadow attributes, all
    attributes supported as integers only.
    '''

    if 'shadow.info' in __salt__:
        lshad = __salt__['shadow.info'](name)

    lusr = __salt__['user.info'](name)
    if not lusr:
        return False

    change = {}
    wanted_groups = sorted(set((groups or []) + (optional_groups or [])))
    if uid:
        if lusr['uid'] != uid:
            change['uid'] = uid
    if gid is not None:
        if lusr['gid'] not in (gid, __salt__['file.group_to_gid'](gid)):
            change['gid'] = gid
    default_grp = __salt__['file.gid_to_group'](
        gid if gid is not None else lusr['gid']
    )
    # remove the default group from the list for comparison purposes
    if default_grp in lusr['groups']:
        lusr['groups'].remove(default_grp)
    if name in lusr['groups'] and name not in wanted_groups:
        lusr['groups'].remove(name)
    # remove default group from wanted_groups, as this requirement is
    # already met
    if default_grp in wanted_groups:
        wanted_groups.remove(default_grp)
    if _group_changes(lusr['groups'], wanted_groups, remove_groups):
        change['groups'] = wanted_groups
    if home:
        if lusr['home'] != home:
            change['home'] = home
        if createhome and not os.path.isdir(home):
            change['homeDoesNotExist'] = home

    if shell:
        if lusr['shell'] != shell:
            change['shell'] = shell
    if 'shadow.info' in __salt__ and 'shadow.default_hash' in __salt__:
        if password:
            default_hash = __salt__['shadow.default_hash']()
            if lshad['passwd'] == default_hash \
                    or lshad['passwd'] != default_hash and enforce_password:
                if lshad['passwd'] != password:
                    change['passwd'] = password
        if date and date is not 0 and lshad['lstchg'] != date:
            change['date'] = date
        if mindays and mindays is not 0 and lshad['min'] != mindays:
            change['mindays'] = mindays
        if maxdays and maxdays is not 999999 and lshad['max'] != maxdays:
            change['maxdays'] = maxdays
        if inactdays and inactdays is not 0 and lshad['inact'] != inactdays:
            change['inactdays'] = inactdays
        if warndays and warndays is not 7 and lshad['warn'] != warndays:
            change['warndays'] = warndays
        if expire and expire is not -1 and lshad['expire'] != expire:
            change['expire'] = expire
    # GECOS fields
    if fullname is not None and lusr['fullname'] != fullname:
        change['fullname'] = fullname
    # MacOS doesn't have full GECOS support, so check for the "ch" functions
    # and ignore these parameters if these functions do not exist.
    if 'user.chroomnumber' in __salt__:
        if roomnumber is not None and lusr['roomnumber'] != roomnumber:
            change['roomnumber'] = roomnumber
    if 'user.chworkphone' in __salt__:
        if workphone is not None and lusr['workphone'] != workphone:
            change['workphone'] = workphone
    if 'user.chhomephone' in __salt__:
        if homephone is not None and lusr['homephone'] != homephone:
            change['homephone'] = homephone

    return change


def present(name,
            uid=None,
            gid=None,
            gid_from_name=False,
            groups=None,
            optional_groups=None,
            remove_groups=True,
            home=None,
            createhome=True,
            password=<PASSWORD>,
            enforce_password=True,
            empty_password=<PASSWORD>,
            shell=None,
            unique=True,
            system=False,
            fullname=None,
            roomnumber=None,
            workphone=None,
            homephone=None,
            date=None,
            mindays=None,
            maxdays=None,
            inactdays=None,
            warndays=None,
            expire=None):
    '''
    Ensure that the named user is present with the specified properties

    name
        The name of the user to manage

    uid
        The user id to assign, if left empty then the next available user id
        will be assigned

    gid
        The default group id

    gid_from_name
        If True, the default group id will be set to the id of the group with
        the same name as the user.

    groups
        A list of groups to assign the user to, pass a list object. If a group
        specified here does not exist on the minion, the state will fail.
        If set to the empty list, the user will be removed from all groups
        except the default group.

    optional_groups
        A list of groups to assign the user to, pass a list object. If a group
        specified here does not exist on the minion, the state will silently
        ignore it.

    NOTE: If the same group is specified in both "groups" and
    "optional_groups", then it will be assumed to be required and not optional.

    remove_groups
        Remove groups that the user is a member of that weren't specified in
        the state, True by default

    home
        The location of the home directory to manage

    createhome
        If True, the home directory will be created if it doesn't exist.
        Please note that directories leading up to the home directory
        will NOT be created.

    password
        A password hash to set for the user. This field is only supported on
        Linux, FreeBSD, NetBSD, OpenBSD, and Solaris.

    .. versionchanged:: 0.16.0
       BSD support added.

    enforce_password
        Set to False to keep the password from being changed if it has already
        been set and the password hash differs from what is specified in the
        "password" field. This option will be ignored if "password" is not
        specified.

    empty_password
        Set to True to enable no password-less login for user

    shell
        The login shell, defaults to the system default shell

    unique
        Require a unique UID, True by default

    system
        Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.


    User comment field (GECOS) support (currently Linux, FreeBSD, and MacOS
    only):

    The below values should be specified as strings to avoid ambiguities when
    the values are loaded. (Especially the phone and room number fields which
    are likely to contain numeric data)

    fullname
        The user's full name

    roomnumber
        The user's room number (not supported in MacOS)

    workphone
        The user's work phone number (not supported in MacOS)

    homephone
        The user's home phone number (not supported in MacOS)


    .. versionchanged:: 2014.7.0
       Shadow attribute support added.

    Shadow attributes support (currently Linux only):

    The below values should be specified as integers.

    date
        Date of last change of password, represented in days since epoch
        (January 1, 1970).

    mindays
        The minimum number of days between password changes.

    maxdays
        The maximum number of days between password changes.

    inactdays
        The number of days after a password expires before an account is
        locked.

    warndays
        Number of days prior to maxdays to warn users.

    expire
        Date that account expires, represented in days since epoch (January 1,
        1970).
    '''
    fullname = str(fullname) if fullname is not None else fullname
    roomnumber = str(roomnumber) if roomnumber is not None else roomnumber
    workphone = str(workphone) if workphone is not None else workphone
    homephone = str(homephone) if homephone is not None else homephone

    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'User {0} is present and up to date'.format(name)}

    if groups:
        missing_groups = [x for x in groups if not __salt__['group.info'](x)]
        if missing_groups:
            ret['comment'] = 'The following group(s) are not present: ' \
                             '{0}'.format(','.join(missing_groups))
            ret['result'] = False
            return ret

    if optional_groups:
        present_optgroups = [x for x in optional_groups
                             if __salt__['group.info'](x)]
        for missing_optgroup in [x for x in optional_groups<commit_msg>fall back to utf-8 if we get a unicode error<commit_after># -*- coding: utf-8 -*-
'''
Management of user accounts
===========================

The user module is used to create and manage user settings, users can be set
as either absent or present

.. code-block:: yaml

    fred:
      user.present:
        - fullname: <NAME>
        - shell: /bin/zsh
        - home: /home/fred
        - uid: 4000
        - gid: 4000
        - groups:
          - wheel
          - storage
          - games

    testuser:
      user.absent
'''

# Import python libs
import logging
import os

# Import salt libs
import salt.utils

log = logging.getLogger(__name__)


def _group_changes(cur, wanted, remove=False):
    '''
    Determine if the groups need to be changed
    '''
    old = set(cur)
    new = set(wanted)
    if (remove and old != new) or (not remove and not new.issubset(old)):
        return True
    return False


def _changes(name,
             uid=None,
             gid=None,
             groups=None,
             optional_groups=None,
             remove_groups=True,
             home=None,
             createhome=True,
             password=<PASSWORD>,
             enforce_password=True,
             empty_password=False,
             shell=None,
             fullname='',
             roomnumber='',
             workphone='',
             homephone='',
             date=0,
             mindays=0,
             maxdays=999999,
             inactdays=0,
             warndays=7,
             expire=-1):
    '''
    Return a dict of the changes required for a user if the user is present,
    otherwise return False.

    Updated in 2014.7.0 to include support for shadow attributes, all
    attributes supported as integers only.
    '''

    if 'shadow.info' in __salt__:
        lshad = __salt__['shadow.info'](name)

    lusr = __salt__['user.info'](name)
    if not lusr:
        return False

    change = {}
    wanted_groups = sorted(set((groups or []) + (optional_groups or [])))
    if uid:
        if lusr['uid'] != uid:
            change['uid'] = uid
    if gid is not None:
        if lusr['gid'] not in (gid, __salt__['file.group_to_gid'](gid)):
            change['gid'] = gid
    default_grp = __salt__['file.gid_to_group'](
        gid if gid is not None else lusr['gid']
    )
    # remove the default group from the list for comparison purposes
    if default_grp in lusr['groups']:
        lusr['groups'].remove(default_grp)
    if name in lusr['groups'] and name not in wanted_groups:
        lusr['groups'].remove(name)
    # remove default group from wanted_groups, as this requirement is
    # already met
    if default_grp in wanted_groups:
        wanted_groups.remove(default_grp)
    if _group_changes(lusr['groups'], wanted_groups, remove_groups):
        change['groups'] = wanted_groups
    if home:
        if lusr['home'] != home:
            change['home'] = home
        if createhome and not os.path.isdir(home):
            change['homeDoesNotExist'] = home

    if shell:
        if lusr['shell'] != shell:
            change['shell'] = shell
    if 'shadow.info' in __salt__ and 'shadow.default_hash' in __salt__:
        if password:
            default_hash = __salt__['shadow.default_hash']()
            if lshad['passwd'] == default_hash \
                    or lshad['passwd'] != default_hash and enforce_password:
                if lshad['passwd'] != password:
                    change['passwd'] = password
        if date and date is not 0 and lshad['lstchg'] != date:
            change['date'] = date
        if mindays and mindays is not 0 and lshad['min'] != mindays:
            change['mindays'] = mindays
        if maxdays and maxdays is not 999999 and lshad['max'] != maxdays:
            change['maxdays'] = maxdays
        if inactdays and inactdays is not 0 and lshad['inact'] != inactdays:
            change['inactdays'] = inactdays
        if warndays and warndays is not 7 and lshad['warn'] != warndays:
            change['warndays'] = warndays
        if expire and expire is not -1 and lshad['expire'] != expire:
            change['expire'] = expire
    # GECOS fields
    if fullname is not None and lusr['fullname'] != fullname:
        change['fullname'] = fullname
    # MacOS doesn't have full GECOS support, so check for the "ch" functions
    # and ignore these parameters if these functions do not exist.
    if 'user.chroomnumber' in __salt__:
        if roomnumber is not None and lusr['roomnumber'] != roomnumber:
            change['roomnumber'] = roomnumber
    if 'user.chworkphone' in __salt__:
        if workphone is not None and lusr['workphone'] != workphone:
            change['workphone'] = workphone
    if 'user.chhomephone' in __salt__:
        if homephone is not None and lusr['homephone'] != homephone:
            change['homephone'] = homephone

    return change


def present(name,
            uid=None,
            gid=None,
            gid_from_name=False,
            groups=None,
            optional_groups=None,
            remove_groups=True,
            home=None,
            createhome=True,
            password=<PASSWORD>,
            enforce_password=True,
            empty_password=False,
            shell=None,
            unique=True,
            system=False,
            fullname=None,
            roomnumber=None,
            workphone=None,
            homephone=None,
            date=None,
            mindays=None,
            maxdays=None,
            inactdays=None,
            warndays=None,
            expire=None):
    '''
    Ensure that the named user is present with the specified properties

    name
        The name of the user to manage

    uid
        The user id to assign, if left empty then the next available user id
        will be assigned

    gid
        The default group id

    gid_from_name
        If True, the default group id will be set to the id of the group with
        the same name as the user.

    groups
        A list of groups to assign the user to, pass a list object. If a group
        specified here does not exist on the minion, the state will fail.
        If set to the empty list, the user will be removed from all groups
        except the default group.

    optional_groups
        A list of groups to assign the user to, pass a list object. If a group
        specified here does not exist on the minion, the state will silently
        ignore it.

    NOTE: If the same group is specified in both "groups" and
    "optional_groups", then it will be assumed to be required and not optional.

    remove_groups
        Remove groups that the user is a member of that weren't specified in
        the state, True by default

    home
        The location of the home directory to manage

    createhome
        If True, the home directory will be created if it doesn't exist.
        Please note that directories leading up to the home directory
        will NOT be created.

    password
        A password hash to set for the user. This field is only supported on
        Linux, FreeBSD, NetBSD, OpenBSD, and Solaris.

    .. versionchanged:: 0.16.0
       BSD support added.

    enforce_password
        Set to False to keep the password from being changed if it has already
        been set and the password hash differs from what is specified in the
        "password" field. This option will be ignored if "password" is not
        specified.

    empty_password
        Set to True to enable no password-less login for user

    shell
        The login shell, defaults to the system default shell

    unique
        Require a unique UID, True by default

    system
        Choose UID in the range of FIRST_SYSTEM_UID and LAST_SYSTEM_UID.


    User comment field (GECOS) support (currently Linux, FreeBSD, and MacOS
    only):

    The below values should be specified as strings to avoid ambiguities when
    the values are loaded. (Especially the phone and room number fields which
    are likely to contain numeric data)

    fullname
        The user's full name

    roomnumber
        The user's room number (not supported in MacOS)

    workphone
        The user's work phone number (not supported in MacOS)

    homephone
        The user's home phone number (not supported in MacOS)


    .. versionchanged:: 2014.7.0
       Shadow attribute support added.

    Shadow attributes support (currently Linux only):

    The below values should be specified as integers.

    date
        Date of last change of password, represented in days since epoch
        (January 1, 1970).

    mindays
        The minimum number of days between password changes.

    maxdays
        The maximum number of days between password changes.

    inactdays
        The number of days after a password expires before an account is
        locked.

    warndays
        Number of days prior to maxdays to warn users.

    expire
        Date that account expires, represented in days since epoch (January 1,
        1970).
    '''
    try:
        fullname = str(fullname) if fullname is not None else fullname
    except UnicodeEncodeError:
        fullname = fullname.encode('utf-8') if fullname is not None else fullname
    try:
        roomnumber = str(roomnumber) if roomnumber is not None else roomnumber
    except UnicodeEncodeError:
        roomnumber = roomnumber.encode('utf-8') if roomnumber is not None else roomnumber
    try:
        workphone = str(workphone) if workphone is not None else workphone
    except UnicodeEncodeError:
        workphone = workphone.encode('utf-8') if workphone is not None else workphone
    try:
        homephone = str(homephone) if homephone is not None else homephone
    except UnicodeEncodeError:
        homephone = homephone.encode('utf-8') if homephone is not None else homephone

    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'User {0} is present and up to date'.format(name)}

    if groups:
        missing_groups = [x for x in groups if not __salt__['group.info'](x)]
        if missing_groups:
            ret['comment'] = 'The following group(s) are not present: ' \
                             '{0}'.format(','.join(missing_groups))
            ret['result'] = False
            return ret

    if optional_groups:
        present_optgroups = [x for x in optional_groups
                             if __salt__['group.info'](x)]
        for missing_optgroup in [x for x in optional_groups
{'fraction_non_alphanumeric': 0.0775584009383247, 'fraction_numerical': 0.006011142605805884, 'mean_word_length': 2.0868909337758335, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '70611', 'n_tokens_mistral': 5703, 'n_tokens_neox': 5401, 'n_words': 2158}