# -*- coding: utf-8 -*-
###########################################################################
# Copyright (C) 2003, 2004 by Wido Depping
# <widod@users.sourceforge.net>
#
# Copyright: See COPYING file that comes with this distribution
#
###########################################################################
import ldap
import ldap.schema
import ldapurl
import re
from sets import Set
from copy import copy
import threading
import time
from base.backend.ServerList import ServerList
import environment
from base.utils.backend.LogObject import LogObject
SCHEMA_CLASS_MAPPING = ldap.cidict.cidict()
SCHEMA_ATTRS = SCHEMA_CLASS_MAPPING.keys()
class UnknownAttribute(object):
pass
class ObjectClassAttributeInfo(object):
""" A class for getting information about objectClassesDict and attributes
from a server.
"""
# schema-cache of all servers whose schema has been requested already
serverMetaCache = {}
def __init__(self, serverMeta=None):
# Dictionaries with lowercase names of attributes and objectclasses
self.objectClassesDict = {}
self.attributeDict = {}
self.matchingDict = {}
self.syntaxDict = {}
# Indicates if an error has occured while fetching the schemas
self.failure = False
# Exception object why fetching schema failed
self.failureException = None
# alias name of the current server
self.serverMeta = serverMeta
# automaticly retrieve schema info if server data is passed
# upon initialization
if not (serverMeta == None):
self.retrieveInfoFromServer()
###############################################################################
def retrieveInfoFromServer(self):
""" Retrieve all information of objectClassesDict and attributes from the
server.
"""
environment.setBusy(True)
# Try to get server schema information from the cache
if self.serverMeta.name in self.serverMetaCache.keys():
self.objectClassesDict = self.serverMetaCache[self.serverMeta.name]["objectClassesDict"]
self.attributeDict = self.serverMetaCache[self.serverMeta.name]["attributeDict"]
self.syntaxDict = self.serverMetaCache[self.serverMeta.name]["syntaxDict"]
self.matchingDict = self.serverMetaCache[self.serverMeta.name]["matchingDict"]
else:
tmpObject = ServerList()
tmpObject.readServerList()
serverMeta = self.serverMeta
workerThread = WorkerThreadFetch(serverMeta)
workerThread.start()
while not workerThread.FINISHED:
environment.updateUI()
time.sleep(0.05)
if None == workerThread.exceptionObject:
self.objectClassesDict = workerThread.objectClassesDict
self.attributeDict = workerThread.attributeDict
self.syntaxDict = workerThread.syntaxDict
self.matchingDict = workerThread.matchingDict
# store retrieved information in the cache
metaData = {}
metaData['objectClassesDict'] = self.objectClassesDict
metaData['attributeDict'] = self.attributeDict
metaData['syntaxDict'] = self.syntaxDict
metaData['matchingDict'] = self.matchingDict
self.__class__.serverMetaCache[self.serverMeta.name] = metaData
tmpString = "Schema information for server " + self.serverMeta.name + " retrieved."
environment.logMessage(LogObject("Info", tmpString))
else:
self.failure = True
self.failureException = workerThread.exceptionObject
tmpString = "Could not fetch LDAP schema from server. Reason:\n"
tmpString += str(workerThread.exceptionObject)
environment.logMessage(LogObject("Error", tmpString))
environment.setBusy(False)
###############################################################################
def getAttributeList(self):
""" Returns a list of all attributes the server supports.
"""
return map(lambda x: x["NAME"], self.attributeDict.values())
###############################################################################
def getAllAttributes(self, classList = None):
""" Return two sets of all attributes which the server supports for the
given classList.
"""
must = Set()
may = Set()
classList = self.getClassesWithParents(classList)
for x in classList:
x = x.lower()
if not x in self.objectClassesDict:
continue
must |= Set(self.objectClassesDict[x]["MUST"])
may |= Set(self.objectClassesDict[x]["MAY"])
return must, may
###############################################################################
def getAllMusts(self, classList = None):
""" Returns a set of all attributes which are needed by the
objectClassesDict given by classList.
"""
must = Set()
classList = self.getClassesWithParents(classList)
for x in classList:
must |= Set(self.objectClassesDict[x.lower()]["MUST"])
return must
###############################################################################
def getAllMays(self, classList = None):
""" Returns a set of all attributes which are optional for the
objectClassesDict given by classList.
"""
may = Set()
classList = self.getClassesWithParents(classList)
for x in classList:
may |= Set(self.objectClassesDict[x.lower()]["MAY"])
return may
###############################################################################
def getAllObjectclassesForAttr(self,attribute=""):
""" Returns two sets of objectClasses that either MUST
or MAY use the given attribute
"""
must = Set()
may = Set()
attribute = attribute.lower()
if ";binary" == attribute[-7:]:
attribute = attribute[:-7]
for (key,value) in self.objectClassesDict.items():
for x in value['MUST']:
if attribute == x.lower():
must.add(self.objectClassesDict[key]["NAME"])
for x in value['MAY']:
if attribute == x.lower():
may.add(self.objectClassesDict[key]["NAME"])
return must, may
###############################################################################
def isSingle(self, attribute = ""):
""" Check if an attribute is single.
"""
attribute = attribute.lower()
if ";binary" == attribute[-7:]:
attribute = attribute[:-7]
if attribute in self.attributeDict:
return self.attributeDict[attribute]["SINGLE"]
else:
return False
###############################################################################
def isMust(self, attribute="", classList = None):
""" Check if the given attribute must be set.
"""
if classList == None:
raise "Missing Arguments to Funktion 'isMust(attribute, objectClassesDict)"
attribute = attribute.lower()
if ";binary" == attribute[-7:]:
attribute = attribute[:-7]
classList = self.getClassesWithParents(classList)
value = False
for x in classList:
tmpList = self.objectClassesDict[x.lower()]["MUST"]
tmpList = map(lambda tmpString: tmpString.lower(), tmpList)
if attribute in tmpList:
value = True
break
return value
###############################################################################
def isStructural(self, objectClass):
""" Check if the given objectClass is structural.
"""
objectClass = objectClass.lower()
if "STRUCTURAL" == self.objectClassesDict[objectClass]["KIND"]:
return True
else:
return False
###############################################################################
def isBinary(self, attribute=""):
""" Check if the given attribute has binary values.
"""
retVal = False
attribute = attribute.lower()
if attribute in self.attributeDict:
syntax = self.attributeDict[attribute]["SYNTAX"]
if syntax in ldap.schema.NOT_HUMAN_READABLE_LDAP_SYNTAXES :
retVal = True
if ";binary" == attribute[-7:]:
retVal = True
return retVal
###############################################################################
def hasObjectClass(self, objectClass):
""" Returns a boolean wether the given objectClass is present
in the schema.
"""
return self.objectClassesDict.has_key(objectClass.lower())
###############################################################################
def hasAttribute(self, attribute):
""" Returns a boolean wether the given attribute is present
in the schema.
"""
if attribute[-7:] == ";binary":
attribute = attribute[:-7]
return self.attributeDict.has_key(attribute.lower())
###############################################################################
def getObjectClasses(self):
""" Returns a list of all objectClasses the schema supports.
"""
return map(lambda x: self.objectClassesDict[x]["NAME"],self.objectClassesDict.keys())
###############################################################################
def matchingRule(self, attribute):
""" Return which syntax the attribute has.
"""
attribute = attribute.lower()
if ";binary" == attribute[-7:]:
attribute = attribute[:-7]
if not self.attributeDict.has_key(attribute):
return None
return self.attributeDict[attribute]["SYNTAX"]
###############################################################################
def getParents(self, className=None):
""" Returns the complete parentclass chain for className, except 'top'.
"""
if None == className:
return None
className = className.lower()
if "top" == className:
return []
parentList = []
tmpList = copy(self.objectClassesDict[className]["PARENTS"])
while len(tmpList) > 0:
currentClass = tmpList[0].lower()
tmpList += self.objectClassesDict[currentClass]["PARENTS"]
parentList.append(self.objectClassesDict[currentClass]['NAME'])
del tmpList[0]
return parentList
###############################################################################
def sameObjectClassChain(self, firstClass, secondClass):
""" Returns if two objectClasses belong to the same class chain.
"""
firstClass = firstClass.lower()
secondClass = secondClass.lower()
firstParents = self.getParents(firstClass)
secondParents = self.getParents(secondClass)
if self.objectClassesDict[firstClass]['NAME'] in secondParents:
return True
elif self.objectClassesDict[secondClass]['NAME'] in firstParents:
return True
else:
return False
###############################################################################
def getObjectClassChain(self, className, classList):
""" Returns a list of objectClasses which belong to the same chain
as className, given by classList.
"""
tmpList = []
for x in classList:
if self.sameObjectClassChain(className, x):
tmpList.append(x)
return tmpList
###############################################################################
def classAllowed(self, className, classList):
""" Returns if className can be added to classList.
"""
allowedBool = True
for x in classList:
if not self.sameObjectClassChain(className, x):
allowedBool = False
return allowedBool
###############################################################################
def getClassesWithParents(self, classList):
""" Returns the given classes together with their parents in a set.
"""
classSet = Set()
for x in classList:
tmpList = self.getParents(x)
classSet.add(x)
for y in tmpList:
classSet.add(y)
return classSet
###############################################################################
def attributeAllowed(self, attributeName, classList):
""" Returns a boolean if the attribute attributeName is allowed with the
classes given by classList.
"""
classList = self.getClassesWithParents(classList)
mustSet, maySet = self.getAllAttributes(classList)
newSet = mustSet.union(maySet)
if ";binary" == attributeName[-7:]:
attributeName = attributeName[:-7]
if attributeName in newSet:
return True
else:
return False
###############################################################################
def getSyntaxList(self):
""" Returns the list of supported syntaxes by the server.
"""
return self.syntaxDict.keys()
###############################################################################
def getAttributeListForSyntax(self, syntaxString):
""" Returns a list of attributes which use the syntax given
by syntaxString.
"""
tmpList = self.getAttributeList()
attributeList = []
for x in tmpList:
dataDict = self.attributeDict[x.lower()]
if dataDict["SYNTAX"] == syntaxString:
attributeList.append(x)
return attributeList
###############################################################################
def getMachtingListForSyntax(self, syntaxString):
""" Returns a list of matching rules for the given syntax.
"""
tmpList = self.getMachtingList()
matchingList = []
for x in tmpList:
dataDict = self.matchingDict[x.lower()]
if dataDict["SYNTAX"] == syntaxString:
matchingList.append(x)
return matchingList
###############################################################################
def getMachtingList(self):
""" Returns a list of matching rules supported by the server.
"""
return map(lambda x: self.matchingDict[x]['NAME'], self.matchingDict.keys())
###############################################################################
def getMatchingRuleList(self):
""" Returns a list of matching rules supported by the server.
"""
return map(lambda x: self.matchingDict[x]['NAME'], self.matchingDict.keys())
###############################################################################
def getAttributeListForMatchingRule(self, matchingString):
""" Returns a list of attributes which use the matching rule given
by matchingString.
"""
tmpList = self.getAttributeList()
attributeList = []
for x in tmpList:
dataDict = self.attributeDict[x.lower()]
if dataDict["EQUALITY"] == matchingString:
attributeList.append(x)
return attributeList
###############################################################################
def getAttributeListForObjectClass(self, objectClass):
""" Returns a list of attributes which belong to objectClass.
"""
objectClass = objectClass.lower()
tmpList = []
attributeDict = self.objectClassesDict[objectClass]["MUST"]
for x in attributeDict:
tmpList.append(x)
attributeDict = self.objectClassesDict[objectClass]["MAY"]
for x in attributeDict:
tmpList.append(x)
return tmpList
###############################################################################
###############################################################################
class WorkerThreadFetch(threading.Thread):
def __init__(self, serverMeta):
threading.Thread.__init__(self)
self.serverMeta = serverMeta
self.ldapConnection = None
self.FINISHED = False
self.exceptionObject = None
self.objectClassesDict = {}
self.attributeDict = {}
self.syntaxDict = {}
self.matchingDict = {}
def run(self):
try:
urlschemeVal = "ldap"
if self.serverMeta.encryptionMethod == "SSL":
urlschemeVal = "ldaps"
whoVal = None
credVal = None
if not (self.serverMeta.bindAnon):
whoVal = self.serverMeta.bindDN
credVal = self.serverMeta.bindPassword
url = ldapurl.LDAPUrl(urlscheme=urlschemeVal,
hostport = self.serverMeta.host + ":" + str(self.serverMeta.port),
dn = self.serverMeta.baseDN, who = whoVal,
cred = credVal)
self.ldapServerObject = ldap.initialize(url.initializeUrl())
self.ldapServerObject.protocol_version = 3
# Check whether we want to validate the server certificate.
validateMethod = ldap.OPT_X_TLS_DEMAND
if self.serverMeta.checkServerCertificate == u"demand":
validateMethod = ldap.OPT_X_TLS_DEMAND
elif self.serverMeta.checkServerCertificate == u"never":
validateMethod = ldap.OPT_X_TLS_NEVER
elif self.serverMeta.checkServerCertificate == u"try":
validateMethod = ldap.OPT_X_TLS_TRY
elif self.serverMeta.checkServerCertificate == u"allow":
validateMethod = ldap.OPT_X_TLS_ALLOW
encryption = False
if self.serverMeta.encryptionMethod == "SSL":
encryption = True
elif self.serverMeta.encryptionMethod == "TLS":
encryption = True
if encryption:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, validateMethod)
#self.ldapServerObject.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, validateMethod)
# If we're going to present client certificates, this must be set as an option
if self.serverMeta.useCertificate and encryption:
try:
self.ldapServerObject.set_option(ldap.OPT_X_TLS_CERTFILE,self.serverMeta.clientCertFile)
self.ldapServerObject.set_option(ldap.OPT_X_TLS_KEYFILE,self.serverMeta.clientCertKeyfile)
except Exception, e:
message = "Certificate error. Reason:\n"
message += "Could not set client certificate and certificate keyfile. "
message += str(e)
environment.logMessage(LogObject("Error,",message))
if self.serverMeta.encryptionMethod == "TLS":
self.ldapServerObject.start_tls_s()
# Enable Alias support
if self.serverMeta.followAliases:
self.ldapServerObject.set_option(ldap.OPT_DEREF, ldap.DEREF_ALWAYS)
if self.serverMeta.bindAnon:
self.ldapServerObject.simple_bind()
elif self.serverMeta.authMethod == u"Simple":
self.ldapServerObject.simple_bind_s(whoVal, credVal)
elif u"SASL" in self.serverMeta.authMethod:
sasl_cb_value_dict = {}
if not u"GSSAPI" in self.serverMeta.authMethod:
sasl_cb_value_dict[ldap.sasl.CB_AUTHNAME] = whoVal
sasl_cb_value_dict[ldap.sasl.CB_PASS] = credVal
sasl_mech = None
if self.serverMeta.authMethod == u"SASL Plain":
sasl_mech = "PLAIN"
elif self.serverMeta.authMethod == u"SASL CRAM-MD5":
sasl_mech = "CRAM-MD5"
elif self.serverMeta.authMethod == u"SASL DIGEST-MD5":
sasl_mech = "DIGEST-MD5"
elif self.serverMeta.authMethod == u"SASL Login":
sasl_mech = "LOGIN"
elif self.serverMeta.authMethod == u"SASL GSSAPI":
sasl_mech = "GSSAPI"
elif self.serverMeta.authMethod == u"SASL EXTERNAL":
sasl_mech = "EXTERNAL"
sasl_auth = ldap.sasl.sasl(sasl_cb_value_dict,sasl_mech)
# If python-ldap has no support for SASL, it doesn't have
# sasl_interactive_bind_s as a method.
try:
if "EXTERNAL" == sasl_mech:
#url = ldapurl.LDAPUrl(urlscheme="ldapi",
# hostport = self.serverMeta.host.replace("/", "%2f"),
# dn = self.serverMeta.baseDN)
url = "ldapi://" + self.serverMeta.host.replace("/", "%2F").replace(",", "%2C")
#self.ldapServerObject = ldap.initialize(url.initializeUrl())
self.ldapServerObject = ldap.initialize(url)
self.ldapServerObject.protocol_version = 3
# Enable Alias support
if self.serverMeta.followAliases:
self.ldapServerObject.set_option(ldap.OPT_DEREF, ldap.DEREF_ALWAYS)
self.ldapServerObject.sasl_interactive_bind_s("", sasl_auth)
except AttributeError, e:
self.result = False
self.exceptionObject = e
self.FINISHED = True
return
#subschemasubentry_dn = self.ldapServerObject.search_subschemasubentry_s(url.dn)
subschemasubentry_dn = self.ldapServerObject.search_subschemasubentry_s()
if subschemasubentry_dn is None:
subschemasubentry_entry = None
else:
if url.attrs is None:
schema_attrs = SCHEMA_ATTRS
else:
schema_attrs = url.attrs
subschemasubentry_entry = self.ldapServerObject.read_subschemasubentry_s(
subschemasubentry_dn,attrs=schema_attrs)
self.ldapServerObject.unbind_s()
schema = None
if subschemasubentry_dn!=None:
schema = ldap.schema.SubSchema(subschemasubentry_entry)
else:
schema = None
# get objectclass information
oidList = schema.listall(ldap.schema.ObjectClass)
for x in oidList:
y = schema.get_obj(ldap.schema.ObjectClass, x)
# detect kind of objectclass
kind = ""
if 0 == y.kind:
kind = "STRUCTURAL"
elif 1 == y.kind:
kind = "ABSTRACT"
elif 2 == y.kind:
kind = "AUXILIARY"
# name of objectclass
desc = ""
if not (y.desc == None):
desc = y.desc
# must attributes of the objectclass
must = []
if not (len(y.must) == 0):
must = y.must
# may attributes of the objectclass
may = []
if not (len(y.may) == 0):
may = y.may
# parents of the objectclass
# beware that not the whole class chain is given. only
# the first class above the current
parents = []
for parent in y.sup:
# filter out objectclass top. all classes are
# derived from top
if not ("top" == parent.lower()):
parents.append(parent.lower())
oid = ""
if not (y.oid == None):
oid = y.oid
# store values for each name the current class has.
# IMPORTANT: the key is always lowercase
for name in y.names:
self.objectClassesDict[name.lower()] = {"DESC": desc,
"MUST": must, "MAY": may, "NAME": name, "KIND": kind,
"PARENTS": parents, "OID": oid}
# get attribute information
oidList = schema.listall(ldap.schema.AttributeType)
for x in oidList:
y = schema.get_obj(ldap.schema.AttributeType, x)
nameList = y.names
for z in nameList:
self.attributeDict[z.lower()] = {"DESC": y.desc,
"SINGLE": y.single_value, "SYNTAX": y.syntax,
"NAME": z, "COLLECTIVE": y.collective,
"EQUALITY": y.equality, "OBSOLETE": y.obsolete,
"OID": y.oid, "ORDERING": y.ordering, "SUP": y.sup,
"SYNTAX_LEN": y.syntax_len, "USAGE": y.usage}
# get syntax information
oidList = schema.listall(ldap.schema.LDAPSyntax)
for x in oidList:
y = schema.get_obj(ldap.schema.LDAPSyntax, x)
self.syntaxDict[x] = {"DESC": y.desc, "OID": y.oid}
# get matching information
oidList = schema.listall(ldap.schema.MatchingRule)
for x in oidList:
y = schema.get_obj(ldap.schema.MatchingRule, x)
for z in y.names:
self.matchingDict[z.lower()] = {"DESC": y.desc, "OID": y.oid,
"OBSOLETE": y.obsolete, "SYNTAX": y.syntax,
"NAME": z}
self.FINISHED = True
except Exception, e:
self.FINISHED = True
self.exceptionObject = e
|