# -*- coding: utf-8 -*-
"""
web2ldap plugin classes for OpenLDAP
"""

from w2lapp.schema.syntaxes import \
  DistinguishedName,IA5String,OctetString,DirectoryString,Uri,SelectList, \
  BindDN,MultilineText,UUID,syntax_registry

import re,mspki,ldap.filter,ldap.controls,w2lapp.gui

from pyasn1.codec.ber import decoder as ber_decoder

try:
  from ldapoidreg import oid as oid_desc_reg
except ImportError:
  oid_desc_reg = {}


#---------------------------------------------------------------------------
# slapo-syncprov
#---------------------------------------------------------------------------

# see http://www.openldap.org/faq/data/cache/1145.html
class CSN_SID(IA5String):
  oid = '1.3.6.1.4.1.4203.666.11.2.4'
  desc = 'change sequence number SID (CSN SID)'
  minLen = 3
  maxLen = 3
  reObj = re.compile('^[a-fA-F0-9]{3}$')


# see http://www.openldap.org/faq/data/cache/1145.html
class CSN(IA5String):
  oid = '1.3.6.1.4.1.4203.666.11.2.1'
  desc = 'change sequence number (CSN)'
  minLen = 40
  maxLen = 40
  reObj = re.compile('^[0-9]{14}\\.[0-9]{6}Z#[a-fA-F0-9]{6}#[a-fA-F0-9]{3}#[a-fA-F0-9]{6}$')


syntax_registry.registerAttrType(
  CSN.oid,[
    '1.3.6.1.4.1.4203.666.1.25', # contextCSN
    '1.3.6.1.4.1.4203.666.1.7', # entryCSN
    '1.3.6.1.4.1.4203.666.1.13', # namingCSN
    # also register by name in case OpenLDAP was built without -DSLAP_SCHEMA_EXPOSE
    'contextCSN','entryCSN','namingCSN',
  ]
)


#---------------------------------------------------------------------------
# back-config
#---------------------------------------------------------------------------


class OlcDbIndex(DirectoryString):
  reObj=re.compile("^[a-zA-Z]?[a-zA-Z0-9.,;-]* (pres|eq|sub)(,(pres|eq|sub))*$")


syntax_registry.registerAttrType(
  OlcDbIndex.oid,[
    '1.3.6.1.4.1.4203.1.12.2.3.2.0.2', # olcDbIndex
  ]
)


class OlcSubordinate(SelectList):
  oid = 'OlcSubordinate-oid'
  desc = 'Indicates whether backend is subordinate'
  attr_value_dict = {
    u'':u'FALSE (not set)',
    u'TRUE':u'TRUE',
    u'advertise':u'advertise',
  }

syntax_registry.registerAttrType(
  OlcSubordinate.oid,[
    '1.3.6.1.4.1.4203.1.12.2.3.2.0.15', # olcSubordinate
  ]
)


syntax_registry.registerAttrType(
  BindDN.oid,[
    '1.3.6.1.4.1.4203.1.12.2.3.2.0.8', # olcRootDN
  ]
)


class OlcMultilineText(MultilineText):
  oid = 'OlcMultilineText-oid'
  desc = 'OpenLDAP multiline configuration strings'
  cols = 90
  minInputRows = 2  # minimum number of rows for input field
  whitespace_cleaning = False

  def displayValue(self,valueindex=0,commandbutton=0):
    return '<code>%s</code>' % MultilineText.displayValue(self,valueindex,commandbutton)


syntax_registry.registerAttrType(
  OlcMultilineText.oid,[
    '1.3.6.1.4.1.4203.1.12.2.3.0.1', # olcAccess
    '1.3.6.1.4.1.4203.1.12.2.3.0.6', # olcAuthIDRewrite
    '1.3.6.1.4.1.4203.1.12.2.3.0.8', # olcAuthzRegexp
    '1.3.6.1.4.1.4203.1.12.2.3.2.0.11', # olcSyncrepl
  ]
)


#---------------------------------------------------------------------------
# slapo-accesslog
#---------------------------------------------------------------------------


class AuditContext(DistinguishedName):
  oid = 'AuditContext'
  desc = 'OpenLDAP DN pointing to audit naming context'

  def displayValue(self,valueindex=0,commandbutton=0):
    r = [DistinguishedName.displayValue(self,valueindex,commandbutton)]
    if commandbutton:
      r.extend([
        self._form.applAnchor(
          'search','Audit all DB',self._sid,
          [
            ('dn',self.attrValue),
            ('filterstr','(objectClass=auditObject)'),
            ('scope',str(ldap.SCOPE_ONELEVEL)),
          ],
          title=u'Complete audit trail of current database',
        ),
        self._form.applAnchor(
          'search','Audit writes to DB',self._sid,
          [
            ('dn',self.attrValue),
            ('filterstr','(objectClass=auditWriteObject)'),
            ('scope',str(ldap.SCOPE_ONELEVEL)),
          ],
          title=u'Audit trail of write access to current database',
        ),
      ])
    return ' | '.join(r)

syntax_registry.registerAttrType(
  AuditContext.oid,['1.3.6.1.4.1.4203.666.11.5.1.30','auditContext'] # auditContext
)


class ReqResult(SelectList):
  oid = 'ReqResult-oid'
  desc = 'LDAPv3 declaration of resultCode in (see RFC 4511)'
  attr_value_dict = {
    u'0':u'success',
    u'1':u'operationsError',
    u'2':u'protocolError',
    u'3':u'timeLimitExceeded',
    u'4':u'sizeLimitExceeded',
    u'5':u'compareFalse',
    u'6':u'compareTrue',
    u'7':u'authMethodNotSupported',
    u'8':u'strongerAuthRequired',
    u'9':u'reserved',
    u'10':u'referral',
    u'11':u'adminLimitExceeded',
    u'12':u'unavailableCriticalExtension',
    u'13':u'confidentialityRequired',
    u'14':u'saslBindInProgress',
    u'16':u'noSuchAttribute',
    u'17':u'undefinedAttributeType',
    u'18':u'inappropriateMatching',
    u'19':u'constraintViolation',
    u'20':u'attributeOrValueExists',
    u'21':u'invalidAttributeSyntax',
    u'32':u'noSuchObject',
    u'33':u'aliasProblem',
    u'34':u'invalidDNSyntax',
    u'35':u'reserved for undefined isLeaf',
    u'36':u'aliasDereferencingProblem',
    u'48':u'inappropriateAuthentication',
    u'49':u'invalidCredentials',
    u'50':u'insufficientAccessRights',
    u'51':u'busy',
    u'52':u'unavailable',
    u'53':u'unwillingToPerform',
    u'54':u'loopDetect',
    u'64':u'namingViolation',
    u'65':u'objectClassViolation',
    u'66':u'notAllowedOnNonLeaf',
    u'67':u'notAllowedOnRDN',
    u'68':u'entryAlreadyExists',
    u'69':u'objectClassModsProhibited',
    u'70':u'reserved for CLDAP',
    u'71':u'affectsMultipleDSAs',
    u'80':u'other',
  }

syntax_registry.registerAttrType(
  ReqResult.oid,[
    '1.3.6.1.4.1.4203.666.11.5.1.7','reqResult', # reqResult
  ]
)


class ReqMod(OctetString,DirectoryString):
  oid = 'ReqMod-oid'
  desc = 'List of modifications/old values'
  known_modtypes = set(('+','-','=',''))

  def displayValue(self,valueindex=0,commandbutton=0):
    try:
      mod_attr_type,mod_attr_rest = self.attrValue.split(':',1)
    except ValueError:
      return OctetString.displayValue(self,valueindex,commandbutton)
    mod_type = mod_attr_rest[0]
    if not mod_type==':':
      mod_type = ''
    if not mod_type in self.known_modtypes:
      return OctetString.displayValue(self,valueindex,commandbutton)
    if len(mod_attr_rest)>1:
      try:
        mod_type,mod_attr_value = mod_attr_rest.split(' ',1)
      except ValueError:
        return OctetString.displayValue(self,valueindex,commandbutton)
    else:
      mod_attr_value = ''
    mod_attr_type_u = mod_attr_type.decode(self._ls.charset)
    mod_type_u = mod_type.decode(self._ls.charset)
    try:
      mod_type_u = mod_attr_value.decode(self._ls.charset)
    except UnicodeDecodeError:
      return '%s:%s<br>\n<code>\n%s\n</code>\n' % (
        self._form.utf2display(mod_attr_type_u),
        self._form.utf2display(mod_type_u),
        mspki.util.HexString(
          self.attrValue,
          delimiter=':',wrap=64,linesep='<br>\n'
        )[:-1]
      )
    else:
      return DirectoryString.displayValue(self,valueindex,commandbutton)
    raise ValueError

syntax_registry.registerAttrType(
  ReqMod.oid,[
    '1.3.6.1.4.1.4203.666.11.5.1.16','reqMod',
    '1.3.6.1.4.1.4203.666.11.5.1.17','reqOld',
  ]
)


class ReqControls(IA5String):
  oid = '1.3.6.1.4.1.4203.666.11.5.3.1'
  desc = 'List of LDAPv3 extended controls sent along with a request'

  def displayValue(self,valueindex=0,commandbutton=0):
    result_lines = [IA5String.displayValue(self,valueindex,commandbutton)]
    # Eliminate X-ORDERED prefix
    _,rest = self.attrValue.strip().split('}{',1)
    # check whether it ends with }
    if rest.endswith('}'):
      result_lines.append('Extracted:')
      # consume } and split tokens
      ctrl_tokens = filter(None,[ t.strip() for t in rest[:-1].split(' ') ])
      ctrl_type = ctrl_tokens[0]
      try:
        ctrl_name,_,_ = oid_desc_reg[ctrl_type]
      except (KeyError,ValueError):
        try:
          ctrl_name = ldap.controls.KNOWN_RESPONSE_CONTROLS[ctrl_type].__class__.__name__
        except KeyError:
          ctrl_name = None
      if ctrl_name:
        result_lines.append(self._form.utf2display(ctrl_name.decode('utf-8')))
      # Extract criticality
      try:
        ctrl_criticality = {
          'TRUE':True,
          'FALSE':False,
        }[ctrl_tokens[ctrl_tokens.index('criticality')+1].upper()]
      except (KeyError,ValueError,IndexError):
        ctrl_criticality = False
      result_lines.append('criticality %s' % str(ctrl_criticality).upper())
      # Extract controlValue
      try:
        ctrl_value = ctrl_tokens[ctrl_tokens.index('controlValue')+1].upper()[1:-1].decode('hex')
      except (KeyError,ValueError,IndexError):
        pass
      else:
        try:
          decoded_control_value = ber_decoder.decode(ctrl_value)
        except:
          decoded_control_value = ctrl_value
        result_lines.append('controlValue %s' % (
          self._form.utf2display(
            repr(decoded_control_value).decode('ascii')
          ).replace('\n','<br>')
        ))
    return '<br>'.join(result_lines)

syntax_registry.registerAttrType(
  ReqControls.oid,[
    '1.3.6.1.4.1.4203.666.11.5.1.10','reqControls',
    '1.3.6.1.4.1.4203.666.11.5.1.11','reqRespControls',
  ]
)


class ReqEntryUUID(UUID):
  oid = 'ReqEntryUUID-oid'

  def displayValue(self,valueindex=0,commandbutton=0):
    return ' | '.join((
      UUID.displayValue(self,valueindex,commandbutton),
      self._form.applAnchor(
          'search','Search target',self._sid,
          (
            ('dn',self._dn),
            (
              'filterstr',
              '(entryUUID=%s)' % (self.attrValue.decode('ascii'))
            ),
            ('search_root',self._ls.getSearchRoot(self._ls.uc_decode(self._entry['reqDN'][0])[0])),
          ),
          title=u'Search entry by UUID',
      )
    ))

syntax_registry.registerAttrType(
  ReqEntryUUID.oid,[
    '1.3.6.1.4.1.4203.666.11.5.1.31', # reqEntryUUID
  ]
)


#---------------------------------------------------------------------------
# General
#---------------------------------------------------------------------------


class Authz(DirectoryString):
  oid = '1.3.6.1.4.1.4203.666.2.7'
  desc = 'OpenLDAP authz'


class OpenLDAPACI(DirectoryString):
  oid = '1.3.6.1.4.1.4203.666.2.1'
  desc = 'OpenLDAP ACI'


# Register all syntax classes in this module
for symbol_name in dir():
  syntax_registry.registerSyntaxClass(eval(symbol_name))

syntax_registry.registerAttrType(
  DistinguishedName.oid,[
    'entryDN','monitorConnectionAuthzDN',
    'configContext','monitorContext','reqDN','reqAuthzID'
  ]
)

syntax_registry.registerAttrType(
  Uri.oid,['monitorConnectionListener']
)

