# -*- coding: utf-8 -*-
## GroupUserFolder
## Copyright (C)2006 Ingeniweb

## 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 2 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; see the file COPYING. If not, write to the
## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

"""
__version__ = "$Revision:  $"
# $Source:  $
# $Id: LDAPUserFolderAdapter.py 30098 2006-09-08 12:35:01Z encolpe $
__docformat__ = 'restructuredtext'


from global_symbols import *


# These mandatory attributes are required by LDAP schema.
# They will be filled with user name as a default value.
# You have to provide a gruf_ldap_required_fields python script
# in your Plone's skins if you want to override this.
MANDATORY_ATTRIBUTES = ("sn", "cn", )


def _doAddUser(self, name, password, roles, domains, **kw):
    """
    Special user adding method for use with LDAPUserFolder.
    This will ensure parameters are correct for LDAP management
    """
    kwargs = {}               # We will pass this dict
    attrs = {}

    # Get gruf_ldap_required_fields result and fill in mandatory stuff
    if hasattr(self, "gruf_ldap_required_fields"):
        attrs = self.gruf_ldap_required_fields(login = name)
    else:
        for attr in MANDATORY_ATTRIBUTES:
            attrs[attr] = name
    kwargs.update(attrs)

    # We assume that name is rdn attribute
    rdn_attr = self._rdnattr
    kwargs[rdn_attr] = name

    # Manage password(s)
    kwargs['user_pw'] = password
    kwargs['confirm_pw'] = password

    # Mangle roles
    kwargs['user_roles'] = self._mangleRoles(name, roles)

    # Delegate to LDAPUF default method
    msg = self.manage_addUser(kwargs = kwargs)
    if msg:
        raise RuntimeError, msg


def _doDelUsers(self, names):
    """
    Remove a bunch of users from LDAP.
    We have to call manage_deleteUsers but, before, we need to find their dn.
    """
    dns = []
    for name in names:
        dns.append(self._find_user_dn(name))

    self.manage_deleteUsers(dns)


def _find_user_dn(self, name):
    """
    Convert a name to an LDAP dn
    """
    # Search records matching name
    login_attr = self._login_attr
    v = self.findUser(search_param = login_attr, search_term = name)

    # Filter to keep exact matches only
    v = filter(lambda x: x[login_attr] == name, v)

    # Now, decide what to do
    l = len(v)
    if not l:
        # Invalid name
        raise "Invalid user name: '%s'" % (name, )
    elif l > 1:
        # Several records... don't know how to handle
        raise "Duplicate user name for '%s'" % (name, )
    return v[0]['dn']


def _mangleRoles(self, name, roles):
    """
    Return role_dns for this user
    """
    # Local groups => the easiest part
    if self._local_groups:
        return roles

    # We have to transform roles into group dns: transform them as a dict
    role_dns = []
    all_groups = self.getGroups()
    all_roles = self.valid_roles()
    groups = {}
    for g in all_groups:
        groups[g[0]] = g[1]

    # LDAPUF does the mistake of adding possibly invalid roles to the user roles
    # (for example, adding the cn of a group additionnaly to the mapped zope role).
    # So we must remove from our 'roles' list all roles which are prefixed by group prefix
    # but are not actually groups.
    # See http://www.dataflake.org/tracker/issue_00376 for more information on that
    # particular issue.
    # If a group has the same name as a role, we assume that it should be a _role_.
    # We should check against group/role mapping here, but... well... XXX TODO !
    # See "HERE IT IS" comment below.

    # Scan roles we are asking for to manage groups correctly
    for role in roles:
        if not role in all_roles:
            continue                        # Do not allow propagation of invalid roles
        if role.startswith(GROUP_PREFIX):
            role = role[GROUP_PREFIX_LEN:]          # Remove group prefix : groups are stored WITHOUT prefix in LDAP
            if role in all_roles:
                continue                            # HERE IT IS
        r = groups.get(role, None)
        if not r:
            Log(LOG_WARNING, "LDAP Server doesn't provide a '%s' group (required for user '%s')." % (role, name, ))
        else:
            role_dns.append(r)

    return role_dns


def _doChangeUser(self, name, password, roles, domains, **kw):
    """
    Update a user
    """
    # Find the dn at first
    dn = self._find_user_dn(name)
    
    # Change password
    if password is not None:
        if password == '':
            raise ValueError, "Password must not be empty for LDAP users."
        self.manage_editUserPassword(dn, password)
        
    # Perform role change
    self.manage_editUserRoles(dn, self._mangleRoles(name, roles))

    # (No domain management with LDAP.)

    
def manage_editGroupRoles(self, user_dn, role_dns=[], REQUEST=None):
    """ Edit the roles (groups) of a group """
    from Products.LDAPUserFolder.utils import GROUP_MEMBER_MAP
    try:
        from Products.LDAPUserFolder.LDAPDelegate import ADD, DELETE
    except ImportError:
        # Support for LDAPUserFolder >= 2.6
        ADD = self._delegate.ADD
        DELETE = self._delegate.DELETE

    msg = ""

##    Log(LOG_DEBUG, "assigning", role_dns, "to", user_dn)
    all_groups = self.getGroups(attr='dn')
    cur_groups = self.getGroups(dn=user_dn, attr='dn')
    group_dns = []
    for group in role_dns:
        if group.find('=') == -1:
            group_dns.append('cn=%s,%s' % (group, self.groups_base))
        else:
            group_dns.append(group)

    if self._local_groups:
        if len(role_dns) == 0:
            del self._groups_store[user_dn]
        else:
            self._groups_store[user_dn] = role_dns

    else:
        for group in all_groups:
            member_attr = GROUP_MEMBER_MAP.get(self.getGroupType(group))

            if group in cur_groups and group not in group_dns:
                action = DELETE
            elif group in group_dns and group not in cur_groups:
                action = ADD
            else:
                action = None
            if action is not None:
                msg = self._delegate.modify(
                    group
                    , action
                    , {member_attr : [user_dn]}
                    )
##                Log(LOG_DEBUG, "group", group, "subgroup", user_dn, "result", msg)

    if msg:
        raise RuntimeError, msg
