# -*- coding: utf-8 -*-
"""
w2lapp.groupadm.py: add/delete user entry to/from group entries

web2ldap - a web-based LDAP Client,
see http://www.web2ldap.de for details

(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)
"""

import ldap,ldap.cidict,ldaputil.base, \
       pyweblib.forms,w2lapp.core,w2lapp.gui

from ldaputil.base import SplitRDN

ACTION2MODTYPE = {'add':ldap.MOD_ADD,'remove':ldap.MOD_DELETE}

REQUESTED_GROUP_ATTRS = ['objectClass','cn','description']


def GroupSelectFieldHTML(ls,form,groups_dict,field_name,field_title,group_search_root,dn_list):
  option_list = []
  for dn in dn_list:
#    dn = unicode(dn,ls.charset)
    option_text = unicode(
      groups_dict[dn].get(
        'cn',[dn[:-len(group_search_root) or len(dn)].encode(ls.charset)]
      )[0],
      ls.charset
    )
    option_title = unicode(
      groups_dict[dn].get(
        'description',[dn[:-len(group_search_root)].encode(ls.charset)]
      )[0],
      ls.charset
    )
    option_list.append(
      (u'<option value="%s" title="%s">%s</option>' % (
        dn,option_title,option_text
      )).encode(form.accept_charset)
    )
  return '<select size="15" multiple name="%s" title="%s">\n%s\n</select>\n' % (
    field_name,
    field_title,
    '\n'.join(option_list)
  )


def w2l_GroupAdm(sid,outf,command,form,ls,dn,InfoMsg='',ErrorMsg=''):

  groupadm_defs = ldap.cidict.cidict(w2lapp.cnf.GetParam(ls,'groupadm_defs',{}))
  if not groupadm_defs:
    raise w2lapp.core.ErrorExit(u'Group admin options empty or not set.')
  groupadm_defs_keys = groupadm_defs.keys()

  all_membership_attrs = [
    user_entry_attrtype
    for group_member_attrtype,user_entry_attrtype in groupadm_defs.values()
    if not user_entry_attrtype is None
  ]

  sub_schema = ls.retrieveSubSchema(dn,w2lapp.cnf.GetParam(ls,'_schema',None))

  result_dnlist = ls.readEntry(dn,all_membership_attrs)
  if not result_dnlist:
    raise w2lapp.core.ErrorExit(u'No search result when reading entry.')

  user_entry = ldap.schema.Entry(sub_schema,dn,result_dnlist[0][1])

  # Extract form parameters
  group_search_root = form.getInputValue('groupadm_searchroot',[ls.getSearchRoot(dn)])[0]
  groupadm_view = int(form.getInputValue('groupadm_view',['1'])[0])
  groupadm_name = form.getInputValue('groupadm_name',[None])[0]

  filter_components = []
  for oc in groupadm_defs.keys():
    group_member_attrtype,user_entry_attrtype = groupadm_defs[oc]
    if user_entry_attrtype is None:
      user_entry_attrvalue = dn.encode(ls.charset)
    else:
      try:
        user_entry_attrvalue = user_entry[user_entry_attrtype][0]
      except KeyError:
        continue
    filter_components.append(
      (
        oc.strip(),
        group_member_attrtype.strip(),
        ldap.filter.escape_filter_chars(user_entry_attrvalue)
      )
    )

  #################################################################
  # Search all the group entries
  #################################################################

  groupadm_filterstr_template = w2lapp.cnf.GetParam(ls,'groupadm_filterstr_template',r'(|%s)')

  all_group_filterstr = groupadm_filterstr_template % (''.join(
    [
      '(objectClass=%s)' % (oc)
      for oc,attr_type,attr_value in filter_components
    ]
  ))
  if groupadm_name:
    all_group_filterstr = '(&(cn=*%s*)%s)' % (
      ldap.filter.escape_filter_chars(groupadm_name.encode(ls.charset)),
      all_group_filterstr
    )

  all_groups_dict = {}

  try:
    msg_id = ls.l.search_ext(
      group_search_root.encode(ls.charset),
      ldap.SCOPE_SUBTREE,
      all_group_filterstr,
      attrlist=REQUESTED_GROUP_ATTRS,attrsonly=0,timeout=ls.timeout
    )
    for res_type,res_data,res_msgid,res_controls in ls.l.allresults(msg_id):
      for group_dn,group_entry in res_data:
        if group_dn!=None:
          all_groups_dict[unicode(group_dn,ls.charset)] = ldap.cidict.cidict(group_entry)
  except ldap.NO_SUCH_OBJECT:
    ErrorMsg = 'No such object! Did you choose a valid search base?'
  except (ldap.SIZELIMIT_EXCEEDED,ldap.TIMELIMIT_EXCEEDED):
    ErrorMsg = 'Size limit exceeded while searching group entries! Try to refine search parameters.'

  all_group_entries = all_groups_dict.keys()
  all_group_entries.sort(key=unicode.lower)

  #################################################################
  # Apply changes to group membership
  #################################################################

  if 'groupadm_add' in form.inputFieldNames or \
     'groupadm_remove' in form.inputFieldNames:

    ldaperror_entries = []
    successful_group_mods = []

    for action in ['add','remove']:
      for action_group_dn in form.getInputValue('groupadm_%s'%action,[]):
        group_dn = action_group_dn
        assert all_groups_dict.has_key(group_dn)
        if not all_groups_dict.has_key(group_dn):
          # The group entry could have been removed in the mean time
          # => Ignore that condition
          continue
        modlist = []
        for oc in groupadm_defs_keys:
          if oc.lower() in [ v.lower() for v in all_groups_dict[group_dn]['objectClass'] ]:
            group_member_attrtype,user_entry_attrtype = groupadm_defs[oc]
            if user_entry_attrtype is None:
              member_value = dn.encode(ls.charset)
            else:
              if not user_entry.has_key(user_entry_attrtype):
                raise w2lapp.core.ErrorExit(u"""
                  Object class %s requires entry to have member attribute %s.""" % (
                    oc,user_entry_attrtype
                  )
                )
              member_value = user_entry[user_entry_attrtype][0]
            modlist.append((ACTION2MODTYPE[action],group_member_attrtype,member_value))
        # Finally try to apply group membership modification(s) to single group entry
        try:
          ls.modifyEntry(group_dn,modlist)
        except ldap.LDAPError,e:
          ldaperror_entries.append((group_dn,modlist,w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset)))
        else:
          successful_group_mods.append((group_dn,modlist))

    if successful_group_mods:
      group_add_list = [
        (group_dn,modlist)
        for group_dn,modlist in successful_group_mods
        if modlist and modlist[0][0]==ldap.MOD_ADD
      ]
      group_remove_list = [
        (group_dn,modlist)
        for group_dn,modlist in successful_group_mods
        if modlist and modlist[0][0]==ldap.MOD_DELETE
      ]
      InfoMsg_list = ['<p><strong>Changed group membership</strong></p>']
      if group_add_list:
        InfoMsg_list.append('<p>Added to:</p>')
        InfoMsg_list.extend([
          '%s<br>' % (form.utf2display(group_dn))
          for group_dn,modlist in group_add_list
        ])
      if group_remove_list:
        InfoMsg_list.append('<p>Removed from:</p>')
        InfoMsg_list.extend([
          '%s<br>' % (form.utf2display(group_dn))
          for group_dn,modlist in group_remove_list
        ])
      InfoMsg = '\n'.join(InfoMsg_list)

    if ldaperror_entries:
      ErrorMsg_list = [ErrorMsg]
      ErrorMsg_list.extend([
        'Error while modifying {group_dn}:<br>{error_msg}'.format(
          group_dn=form.utf2display(group_dn),
          error_msg=error_msg
        )
        for group_dn,modlist,error_msg in ldaperror_entries
      ])
      ErrorMsg = '<br>'.join(ErrorMsg_list)

  #################################################################
  # Search for groups the entry is member of
  #################################################################

  remove_group_filterstr = '(|%s)' % (''.join(
    [
      '(&(objectClass=%s)(%s=%s))' % (oc,attr_type,attr_value)
      for oc,attr_type,attr_value in filter_components
    ]
  ))

  remove_groups_dict = {}

  try:
    msg_id = ls.l.search_ext(
      group_search_root.encode(ls.charset),
      ldap.SCOPE_SUBTREE,
      remove_group_filterstr,
      attrlist=REQUESTED_GROUP_ATTRS,attrsonly=0,timeout=ls.timeout
    )
    for res_type,res_data,res_msgid,res_controls in ls.l.allresults(msg_id):
      for group_dn,group_entry in res_data:
        if group_dn!=None:
          remove_groups_dict[unicode(group_dn,ls.charset)] = ldap.cidict.cidict(group_entry)
  except ldap.NO_SUCH_OBJECT,e:
    ErrorMsg = 'No such object! Did you choose a valid search base?'
  except (ldap.SIZELIMIT_EXCEEDED,ldap.TIMELIMIT_EXCEEDED):
    # This should never happen if all groups could be retrieved
    ErrorMsg = 'Size limit exceeded while searching group entries!<br>Try to refine search parameters.'

  remove_group_dns = remove_groups_dict.keys()
  remove_group_dns.sort(key=unicode.lower)

  all_groups_dict.update(remove_groups_dict)

  remove_groups = [ group_dn for group_dn in remove_group_dns ]

  if not all_groups_dict:
    InfoMsg = 'No group entries found. Did you choose a valid search base or valid name?'

  #########################################################
  # Sort out groups the entry is not(!) a member of
  #########################################################

  add_groups = [
    group_dn
    for group_dn in all_group_entries
    if not remove_groups_dict.has_key(group_dn)
  ]

  #########################################################
  # HTML output
  #########################################################

  w2lapp.gui.TopSection(
    sid,outf,form,ls,dn,
    'Group selection table',
    w2lapp.gui.MainMenu(sid,form,ls,dn),
    context_menu_list=[]
  )

  group_search_root_field = w2lapp.gui.SearchRootField(
    form,ls,dn,name='groupadm_searchroot'
  )
  group_search_root_field.charset = form.accept_charset
  group_search_root_field.setDefault(group_search_root)

  outf.write('<div id="Message" class="Main">')

  if ErrorMsg:
    outf.write('<p class="ErrorMessage">%s</p>' % (ErrorMsg))
  if InfoMsg:
    outf.write('<p class="InfoMessage">%s</p>' % (InfoMsg))

  if all_groups_dict:

    outf.write("""
      %s\n%s\n%s\n
        <input type="submit" value="Change Group Membership">
        <table summary="Group select fields">
          <tr>
            <td width="50%%">Add to...</td>
            <td width="50%%">Remove from...</td>
          </tr>
          <tr>
            <td width="50%%">%s</td>
            <td width="50%%">%s</td>
          </tr>
        </table>
      </form>
    """ % (
      # form for changing group membership
      form.beginFormHTML('groupadm',sid,'POST'),
      form.hiddenFieldHTML('dn',dn,u''),
      form.hiddenFieldHTML('groupadm_searchroot',group_search_root,u''),
      GroupSelectFieldHTML(ls,form,all_groups_dict,'groupadm_add','Groups to add to',group_search_root,add_groups),
      GroupSelectFieldHTML(ls,form,remove_groups_dict,'groupadm_remove','Groups to remove from',group_search_root,remove_groups),
    ))

  outf.write("""%s\n%s\n
      <p><input type="submit" value="List"> group entries below: %s.</p>
      <p>where group name contains: %s</p>
      <p>List %s groups.</p>
    </form>
  """ % (
    # form for searching group entries
    form.beginFormHTML('groupadm',sid,'GET'),
    form.hiddenFieldHTML('dn',dn,u''),
    group_search_root_field.inputHTML(title='Search root for searching group entries'),
    form.field['groupadm_name'].inputHTML(),
    form.field['groupadm_view'].inputHTML(title='Group entries list',default=str(groupadm_view)),
  ))

  if groupadm_view:
    outf.write('<dl>\n')
    # Output a legend of all group entries
    for group_dn in {1:remove_groups,2:all_group_entries}[groupadm_view]:
      group_entry = all_groups_dict[group_dn]
      outf.write('<dt>%s | %s</dt>\n<dd>%s<br>\n(%s)<br>\n%s</dd>\n' % (
        ', '.join(group_entry.get('cn',[])),
        form.applAnchor('read','Read',sid,[('dn',group_dn)],title=u'Display group entry'),
        form.utf2display(group_dn),
        ', '.join(group_entry.get('objectClass',[])),
        '<br>'.join(group_entry.get('description',[]))
      ))
    outf.write('</dl>\n')

  outf.write('</div>\n')

  w2lapp.gui.PrintFooter(outf,form)
