Authenticate against an LDAP group

    Behavior is highly dependent on if Active Directory is in use.

    AD handles group membership very differently than OpenLDAP.
    See the :ref:`External Authentication <acl-eauth>` documentation for a thorough
    discussion of available parameters for customizing the search.

    OpenLDAP allows you to search for all groups in the directory
    and returns members of those groups.  Then we check against
    the username entered.

def groups(username, **kwargs):
    '''
    Authenticate against an LDAP group

    Behavior is highly dependent on if Active Directory is in use.

    AD handles group membership very differently than OpenLDAP.
    See the :ref:`External Authentication <acl-eauth>` documentation for a thorough
    discussion of available parameters for customizing the search.

    OpenLDAP allows you to search for all groups in the directory
    and returns members of those groups.  Then we check against
    the username entered.

    '''
    group_list = []

    # If bind credentials are configured, use them instead of user's
    if _config('binddn', mandatory=False) and _config('bindpw', mandatory=False):
        bind = _bind_for_search(anonymous=_config('anonymous', mandatory=False))
    else:
        bind = _bind(username, kwargs.get('password', ''),
                     anonymous=_config('auth_by_group_membership_only', mandatory=False)
                     and _config('anonymous', mandatory=False))

    if bind:
        log.debug('ldap bind to determine group membership succeeded!')

        if _config('activedirectory'):
            try:
                get_user_dn_search = '(&({0}={1})(objectClass={2}))'.format(_config('accountattributename'),
                                                                            username,
                                                                            _config('persontype'))
                user_dn_results = bind.search_s(_config('basedn'),
                                                ldap.SCOPE_SUBTREE,
                                                get_user_dn_search, [str('distinguishedName')])  # future lint: disable=blacklisted-function
            except Exception as e:
                log.error('Exception thrown while looking up user DN in AD: %s', e)
                return group_list
            if not user_dn_results:
                log.error('Could not get distinguished name for user %s', username)
                return group_list
            # LDAP results are always tuples.  First entry in the tuple is the DN
            dn = ldap.filter.escape_filter_chars(user_dn_results[0][0])
            ldap_search_string = '(&(member={0})(objectClass={1}))'.format(dn, _config('groupclass'))
            log.debug('Running LDAP group membership search: %s', ldap_search_string)
            try:
                search_results = bind.search_s(_config('basedn'),
                                               ldap.SCOPE_SUBTREE,
                                               ldap_search_string,
                                               [salt.utils.stringutils.to_str(_config('accountattributename')), str('cn')])  # future lint: disable=blacklisted-function
            except Exception as e:
                log.error('Exception thrown while retrieving group membership in AD: %s', e)
                return group_list
            for _, entry in search_results:
                if 'cn' in entry:
                    group_list.append(salt.utils.stringutils.to_unicode(entry['cn'][0]))
            log.debug('User %s is a member of groups: %s', username, group_list)

        elif _config('freeipa'):
            escaped_username = ldap.filter.escape_filter_chars(username)
            search_base = _config('group_basedn')
            search_string = _render_template(_config('group_filter'), escaped_username)
            search_results = bind.search_s(search_base,
                                           ldap.SCOPE_SUBTREE,
                                           search_string,
                                           [salt.utils.stringutils.to_str(_config('accountattributename')), salt.utils.stringutils.to_str(_config('groupattribute')), str('cn')])  # future lint: disable=blacklisted-function

            for entry, result in search_results:
                for user in itertools.chain(result.get(_config('accountattributename'), []),
                                            result.get(_config('groupattribute'), [])):
                    if username == salt.utils.stringutils.to_unicode(user).split(',')[0].split('=')[-1]:
                        group_list.append(entry.split(',')[0].split('=')[-1])

            log.debug('User %s is a member of groups: %s', username, group_list)

            if not auth(username, kwargs['password']):
                log.error('LDAP username and password do not match')
                return []
        else:
            if _config('groupou'):
                search_base = 'ou={0},{1}'.format(_config('groupou'), _config('basedn'))
            else:
                search_base = '{0}'.format(_config('basedn'))
            search_string = '(&({0}={1})(objectClass={2}))'.format(_config('accountattributename'),
                                                                   username, _config('groupclass'))
            search_results = bind.search_s(search_base,
                                           ldap.SCOPE_SUBTREE,
                                           search_string,
                                           [salt.utils.stringutils.to_str(_config('accountattributename')),
                                            str('cn'),  # future lint: disable=blacklisted-function
                                            salt.utils.stringutils.to_str(_config('groupattribute'))])
            for _, entry in search_results:
                if username in salt.utils.data.decode(entry[_config('accountattributename')]):
                    group_list.append(salt.utils.stringutils.to_unicode(entry['cn'][0]))
            for user, entry in search_results:
                if username == salt.utils.stringutils.to_unicode(user).split(',')[0].split('=')[-1]:
                    for group in salt.utils.data.decode(entry[_config('groupattribute')]):
                        group_list.append(salt.utils.stringutils.to_unicode(group).split(',')[0].split('=')[-1])
            log.debug('User %s is a member of groups: %s', username, group_list)

            # Only test user auth on first call for job.
            # 'show_jid' only exists on first payload so we can use that for the conditional.
            if 'show_jid' in kwargs and not _bind(username, kwargs.get('password'),
                                            anonymous=_config('auth_by_group_membership_only', mandatory=False) and
                                            _config('anonymous', mandatory=False)):
                log.error('LDAP username and password do not match')
                return []
    else:
        log.error('ldap bind to determine group membership FAILED!')

    return group_list