<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12391] twext/trunk/twext/who/ldap</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12391">12391</a></dd>
<dt>Author</dt> <dd>wsanchez@apple.com</dd>
<dt>Date</dt> <dd>2014-01-17 19:10:38 -0800 (Fri, 17 Jan 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>We no longer describe a record type to LDAP as a set of values for a specific attribute (eg. objectType).
Instead, we describe it as a relative distinguished name (presently unused), and a sequence of (attribute, value) pairs to match against.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#twexttrunktwextwholdap_constantspy">twext/trunk/twext/who/ldap/_constants.py</a></li>
<li><a href="#twexttrunktwextwholdap_servicepy">twext/trunk/twext/who/ldap/_service.py</a></li>
<li><a href="#twexttrunktwextwholdap_utilpy">twext/trunk/twext/who/ldap/_util.py</a></li>
<li><a href="#twexttrunktwextwholdaptesttest_servicepy">twext/trunk/twext/who/ldap/test/test_service.py</a></li>
<li><a href="#twexttrunktwextwholdaptesttest_utilpy">twext/trunk/twext/who/ldap/test/test_util.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="twexttrunktwextwholdap_constantspy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/who/ldap/_constants.py (12390 => 12391)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/who/ldap/_constants.py        2014-01-17 23:34:52 UTC (rev 12390)
+++ twext/trunk/twext/who/ldap/_constants.py        2014-01-18 03:10:38 UTC (rev 12391)
</span><span class="lines">@@ -125,6 +125,7 @@
</span><span class="cx"> """
</span><span class="cx"> LDAP match flags.
</span><span class="cx"> """
</span><ins>+ none = ValueConstant(u"")
</ins><span class="cx"> NOT = ValueConstant(u"!")
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="twexttrunktwextwholdap_servicepy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/who/ldap/_service.py (12390 => 12391)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/who/ldap/_service.py        2014-01-17 23:34:52 UTC (rev 12390)
+++ twext/trunk/twext/who/ldap/_service.py        2014-01-18 03:10:38 UTC (rev 12391)
</span><span class="lines">@@ -50,25 +50,6 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-# Maps field name -> LDAP attribute names
-DEFAULT_FIELDNAME_ATTRIBUTE_MAP = MappingProxyType({
- BaseFieldName.guid: (LDAPAttribute.generatedUUID.value,),
- BaseFieldName.recordType: (LDAPAttribute.objectClass.value,),
- BaseFieldName.shortNames: (LDAPAttribute.uid.value,),
- BaseFieldName.fullNames: (LDAPAttribute.cn.value,),
- BaseFieldName.emailAddresses: (LDAPAttribute.mail.value,),
- BaseFieldName.password: (LDAPAttribute.userPassword.value,),
-})
-
-
-# Maps record type -> LDAP object class names
-DEFAULT_RECORDTYPE_OBJECTCLASS_MAP = MappingProxyType({
- BaseRecordType.user: (LDAPObjectClass.inetOrgPerson.value,),
- BaseRecordType.group: (LDAPObjectClass.groupOfNames.value,),
-})
-
-
-
</del><span class="cx"> #
</span><span class="cx"> # Exceptions
</span><span class="cx"> #
</span><span class="lines">@@ -124,6 +105,77 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> #
</span><ins>+# LDAP schema descriptions
+#
+
+class RecordTypeSchema(object):
+ """
+ Describes the LDAP schema for a record type.
+ """
+ def __init__(self, relativeDN, attributes):
+ """
+ @param relativeDN: The relative distinguished name for the record type.
+ This is prepended to the service's base distinguished name when
+ searching for records of this type.
+ @type relativeDN: L{unicode}
+
+ @param attributes: Attribute/value pairs that are expected for records
+ of this type.
+ @type attributes: iterable of sequences containing two L{unicode}s
+ """
+ self.relativeDN = relativeDN
+ self.attributes = tuple(tuple(pair) for pair in attributes)
+
+
+
+# We use strings (constant.value) instead of constants for the values in
+# these mappings because it's meant to be configurable by application users,
+# and user input forms such as config files aren't going to be able to use
+# the constants.
+
+# Maps field name -> LDAP attribute names
+DEFAULT_FIELDNAME_ATTRIBUTE_MAP = MappingProxyType({
+ BaseFieldName.guid: (LDAPAttribute.generatedUUID.value,),
+ BaseFieldName.shortNames: (LDAPAttribute.uid.value,),
+ BaseFieldName.fullNames: (LDAPAttribute.cn.value,),
+ BaseFieldName.emailAddresses: (LDAPAttribute.mail.value,),
+ BaseFieldName.password: (LDAPAttribute.userPassword.value,),
+})
+
+# Information about record types
+DEFAULT_RECORDTYPE_SCHEMAS = MappingProxyType({
+
+ BaseRecordType.user: RecordTypeSchema(
+ # ou=person
+ relativeDN=u"ou={0}".format(LDAPObjectClass.person.value),
+
+ # (objectClass=inetOrgPerson)
+ attributes=(
+ (
+ LDAPAttribute.objectClass.value,
+ LDAPObjectClass.inetOrgPerson.value,
+ ),
+ ),
+ ),
+
+ BaseRecordType.group: RecordTypeSchema(
+ # ou=groupOfNames
+ relativeDN=u"ou={0}".format(LDAPObjectClass.groupOfNames.value),
+
+ # (objectClass=groupOfNames)
+ attributes=(
+ (
+ LDAPAttribute.objectClass.value,
+ LDAPObjectClass.groupOfNames.value,
+ ),
+ ),
+ ),
+
+})
+
+
+
+#
</ins><span class="cx"> # Directory Service
</span><span class="cx"> #
</span><span class="cx">
</span><span class="lines">@@ -148,8 +200,8 @@
</span><span class="cx"> tlsCACertificateFile=None,
</span><span class="cx"> tlsCACertificateDirectory=None,
</span><span class="cx"> useTLS=False,
</span><del>- fieldNameToAttributeMap=DEFAULT_FIELDNAME_ATTRIBUTE_MAP,
- recordTypeToObjectClassMap=DEFAULT_RECORDTYPE_OBJECTCLASS_MAP,
</del><ins>+ fieldNameToAttributesMap=DEFAULT_FIELDNAME_ATTRIBUTE_MAP,
+ recordTypeSchemas=DEFAULT_RECORDTYPE_SCHEMAS,
</ins><span class="cx"> uidField=BaseFieldName.uid,
</span><span class="cx"> _debug=False,
</span><span class="cx"> ):
</span><span class="lines">@@ -176,15 +228,14 @@
</span><span class="cx"> @param useTLS: Enable the use of TLS.
</span><span class="cx"> @type useTLS: L{bool}
</span><span class="cx">
</span><del>- @param fieldNameToAttributeMap: A mapping of field names to LDAP
</del><ins>+ @param fieldNameToAttributesMap: A mapping of field names to LDAP
</ins><span class="cx"> attribute names.
</span><del>- @type fieldNameToAttributeMap: mapping with L{NamedConstant} keys and
</del><ins>+ @type fieldNameToAttributesMap: mapping with L{NamedConstant} keys and
</ins><span class="cx"> sequence of L{unicode} values
</span><span class="cx">
</span><del>- @param recordTypeToObjectClassMap: A mapping of record types to LDAP
- object classes.
- @type recordTypeToObjectClassMap: mapping with L{NamedConstant} keys
- and sequence of L{unicode} values
</del><ins>+ @param recordTypeSchemas: Schema information for record types.
+ @type recordTypeSchemas: mapping from L{NamedConstant} to
+ L{RecordTypeSchema}
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> self.url = url
</span><span class="lines">@@ -209,30 +260,14 @@
</span><span class="cx"> else:
</span><span class="cx"> self._debug = None
</span><span class="cx">
</span><del>- def reverseDict(sourceName, source):
- new = {}
</del><ins>+ if self.fieldName.recordType in fieldNameToAttributesMap:
+ raise TypeError("Record type field may not be mapped")
</ins><span class="cx">
</span><del>- for key, values in source.iteritems():
- for value in values:
- if value in new:
- raise LDAPConfigurationError(
- u"{0} map has duplicate values: {1}"
- .format(sourceName, value)
- )
- new[value] = key
-
- return new
-
- self._fieldNameToAttributeMap = fieldNameToAttributeMap
</del><ins>+ self._fieldNameToAttributesMap = fieldNameToAttributesMap
</ins><span class="cx"> self._attributeToFieldNameMap = reverseDict(
</span><del>- "Field name", fieldNameToAttributeMap
</del><ins>+ "Field name", fieldNameToAttributesMap
</ins><span class="cx"> )
</span><del>-
- self._recordTypeToObjectClassMap = recordTypeToObjectClassMap
- self._objectClassToRecordTypeMap = reverseDict(
- "Record type", recordTypeToObjectClassMap
- )
-
</del><ins>+ self._recordTypeSchemas = recordTypeSchemas
</ins><span class="cx"> self._uidField = uidField
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -312,69 +347,68 @@
</span><span class="cx"> self.log.debug("Performing LDAP query: {query}", query=queryString)
</span><span class="cx">
</span><span class="cx"> reply = connection.search_s(
</span><del>- self._baseDN, ldap.SCOPE_SUBTREE, queryString # attrs
</del><ins>+ self._baseDN, ldap.SCOPE_SUBTREE, queryString # FIXME: attrs
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> records = []
</span><span class="cx">
</span><del>- # Note: self._uidField is the name of the field in
- # self._fieldNameToAttributeMap that tells us which LDAP attribute
</del><ins>+ # self._uidField is the name of the field in
+ # self._fieldNameToAttributesMap that tells us which LDAP attribute
</ins><span class="cx"> # we are using to determine the UID of the record.
</span><span class="cx">
</span><del>- uidField = self.fieldName.uid
- uidAttribute = self._fieldNameToAttributeMap[self._uidField][0]
</del><ins>+ uidAttribute = self._fieldNameToAttributesMap[self._uidField][0]
</ins><span class="cx">
</span><del>- recordTypeField = self.fieldName.recordType
- recordTypeAttributes = (
- self._fieldNameToAttributeMap[self.fieldName.recordType]
- )
</del><ins>+ # recordTypeAttributes = set(chain(*[
+ # info[u"attributes"].iterkeys()
+ # for info in self._recordTypeInfo.itervalues()
+ # ]))
</ins><span class="cx">
</span><span class="cx"> for dn, recordData in reply:
</span><span class="cx">
</span><del>- # Attributes used to determine the record type are required, since
- # record type is very much required.
</del><ins>+ # Fetch the UID
</ins><span class="cx">
</span><del>- for recordTypeAttribute in recordTypeAttributes:
- if recordTypeAttribute not in recordData:
- self.log.debug(
- "Ignoring LDAP record data without record type "
- "attribute {attribute!r}: "
- "{recordData!r}",
- attribute=recordTypeAttribute, recordData=recordData,
- )
- continue
-
- # Make a dict of fields -> values from the incoming dict of
- # attributes -> values.
-
- fields = dict([
- (self._attributeToFieldNameMap[k], v)
- for k, v in recordData.iteritems()
- if k in self._attributeToFieldNameMap
- ])
-
- # Make sure the UID is populated
-
</del><span class="cx"> try:
</span><del>- fields[uidField] = recordData[uidAttribute]
</del><ins>+ uid = recordData[uidAttribute]
</ins><span class="cx"> except KeyError:
</span><span class="cx"> self.log.debug(
</span><del>- "Ignoring LDAP record data with no UID attribute "
- "{source._uidField!r}: {recordData!r}",
</del><ins>+ "Ignoring LDAP record data; no UID attribute "
+ "({source._uidField}): {recordData!r}",
</ins><span class="cx"> recordData=recordData
</span><span class="cx"> )
</span><span class="cx"> continue
</span><span class="cx">
</span><ins>+ # Determine the record type
</ins><span class="cx">
</span><del>- # Coerce data to the correct type
</del><ins>+ recordType = recordTypeForRecordData(
+ self._recordTypeSchemas, recordData
+ )
</ins><span class="cx">
</span><del>- for fieldName, value in fields.iteritems():
</del><ins>+ if recordType is None:
+ self.log.debug(
+ "Ignoring LDAP record data; unable to determine record "
+ "type: {recordData!r}",
+ recordData=recordData,
+ )
+ continue
+
+ # Populate a fields dictionary
+
+ fields = {}
+
+ for attribute, value in recordData.iteritems():
+ fieldName = self._attributeToFieldNameMap.get(attribute, None)
+ if fieldName is None:
+ self.log.debug(
+ "Unmapped LDAP attribute {attribute!r} in record "
+ "data: {recordData!r}",
+ attribute=attribute, recordData=recordData,
+ )
+
</ins><span class="cx"> valueType = self.fieldName.valueType(fieldName)
</span><span class="cx">
</span><del>- if fieldName is recordTypeField:
- value = self._objectClassToRecordTypeMap[value]
- elif valueType in (unicode, UUID):
- value = valueType(value)
</del><ins>+ if valueType in (unicode, UUID):
+ fields[fieldName] = valueType(value)
+
</ins><span class="cx"> else:
</span><span class="cx"> raise LDAPConfigurationError(
</span><span class="cx"> "Unknown value type {0} for field {1}".format(
</span><span class="lines">@@ -382,8 +416,11 @@
</span><span class="cx"> )
</span><span class="cx"> )
</span><span class="cx">
</span><del>- fields[fieldName] = value
</del><ins>+ # Set record type and uid fields
</ins><span class="cx">
</span><ins>+ fields[self.fieldName.recordType] = recordType
+ fields[self.fieldName.uid] = uid
+
</ins><span class="cx"> # Make a record object from fields.
</span><span class="cx">
</span><span class="cx"> record = DirectoryRecord(self, fields)
</span><span class="lines">@@ -398,7 +435,7 @@
</span><span class="cx"> if isinstance(expression, MatchExpression):
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><span class="cx"> expression,
</span><del>- self._fieldNameToAttributeMap, self._recordTypeToObjectClassMap
</del><ins>+ self._fieldNameToAttributesMap, self._recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx"> return self._recordsFromQueryString(queryString)
</span><span class="cx">
</span><span class="lines">@@ -413,7 +450,7 @@
</span><span class="cx">
</span><span class="cx"> queryString = ldapQueryStringFromCompoundExpression(
</span><span class="cx"> expression,
</span><del>- self._fieldNameToAttributeMap, self._recordTypeToObjectClassMap
</del><ins>+ self._fieldNameToAttributesMap, self._recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx"> return self._recordsFromQueryString(queryString)
</span><span class="cx">
</span><span class="lines">@@ -423,3 +460,42 @@
</span><span class="cx"> """
</span><span class="cx"> LDAP directory record.
</span><span class="cx"> """
</span><ins>+
+
+
+def reverseDict(sourceName, source):
+ new = {}
+
+ for key, values in source.iteritems():
+ for value in values:
+ if value in new:
+ raise LDAPConfigurationError(
+ u"{0} map has duplicate values: {1}"
+ .format(sourceName, value)
+ )
+ new[value] = key
+
+ return new
+
+
+def recordTypeForRecordData(recordTypeSchemas, recordData):
+ """
+ Given info about record types, determine the record type for a blob of
+ LDAP record data.
+
+ @param recordTypeSchemas: Schema information for record types.
+ @type recordTypeSchemas: mapping from L{NamedConstant} to
+ L{RecordTypeSchema}
+
+ @param recordData: LDAP record data.
+ @type recordData: mapping
+ """
+
+ for recordType, schema in recordTypeSchemas.iteritems():
+ for attribute, value in schema.attributes:
+ if recordData.get(attribute, None) != value:
+ break
+ else:
+ return recordType
+
+ return None
</ins></span></pre></div>
<a id="twexttrunktwextwholdap_utilpy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/who/ldap/_util.py (12390 => 12391)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/who/ldap/_util.py        2014-01-17 23:34:52 UTC (rev 12390)
+++ twext/trunk/twext/who/ldap/_util.py        2014-01-18 03:10:38 UTC (rev 12391)
</span><span class="lines">@@ -33,7 +33,7 @@
</span><span class="cx"> @type operand: L{unicode}
</span><span class="cx">
</span><span class="cx"> @param queryStrings: LDAP query strings.
</span><del>- @type queryStrings: iterable of L{unicode}
</del><ins>+ @type queryStrings: sequence of L{unicode}
</ins><span class="cx"> """
</span><span class="cx"> if len(queryStrings) == 1:
</span><span class="cx"> return queryStrings[0]
</span><span class="lines">@@ -51,7 +51,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def ldapQueryStringFromMatchExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> ):
</span><span class="cx"> """
</span><span class="cx"> Generates an LDAP query string from a match expression.
</span><span class="lines">@@ -59,15 +59,14 @@
</span><span class="cx"> @param expression: A match expression.
</span><span class="cx"> @type expression: L{MatchExpression}
</span><span class="cx">
</span><del>- @param fieldNameToAttributeMap: A mapping from field names to native LDAP
</del><ins>+ @param fieldNameToAttributesMap: A mapping from field names to native LDAP
</ins><span class="cx"> attribute names.
</span><del>- @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
</del><ins>+ @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
</ins><span class="cx"> of L{unicode} values.
</span><span class="cx">
</span><del>- @param recordTypeToObjectClassMap: A mapping from L{RecordType}s to native
- LDAP object class names.
- @type recordTypeToObjectClassMap: L{dict} with L{RecordType} keys and
- sequence of L{unicode} values.
</del><ins>+ @param recordTypeSchemas: Schema information for record types.
+ @type recordTypeSchemas: mapping from L{NamedConstant} to
+ L{RecordTypeSchema}
</ins><span class="cx">
</span><span class="cx"> @return: An LDAP query string.
</span><span class="cx"> @rtype: L{unicode}
</span><span class="lines">@@ -89,7 +88,7 @@
</span><span class="cx"> notOp = LDAPMatchFlags.NOT.value
</span><span class="cx"> operand = LDAPOperand.AND.value
</span><span class="cx"> else:
</span><del>- notOp = u""
</del><ins>+ notOp = LDAPMatchFlags.none.value
</ins><span class="cx"> operand = LDAPOperand.OR.value
</span><span class="cx">
</span><span class="cx"> # FIXME: It doesn't look like LDAP queries can be case sensitive.
</span><span class="lines">@@ -100,53 +99,63 @@
</span><span class="cx"> # raise NotImplementedError("Need to handle case sensitive")
</span><span class="cx">
</span><span class="cx"> fieldName = expression.fieldName
</span><del>- try:
- attributes = fieldNameToAttributeMap[fieldName]
- except KeyError:
- raise QueryNotSupportedError(
- "Unmapped field name: {0}".format(expression.fieldName)
- )
</del><span class="cx">
</span><span class="cx"> if fieldName is FieldName.recordType:
</span><ins>+ if matchType is not LDAPMatchType.equals:
+ raise QueryNotSupportedError(
+ "Match type for record type must be equals, not {0!r}"
+ .format(expression.matchType)
+ )
+
</ins><span class="cx"> try:
</span><del>- values = recordTypeToObjectClassMap[expression.fieldValue]
</del><ins>+ schema = recordTypeSchemas[expression.fieldValue]
</ins><span class="cx"> except KeyError:
</span><span class="cx"> raise QueryNotSupportedError(
</span><span class="cx"> "Unmapped record type: {0}".format(expression.fieldValue)
</span><span class="cx"> )
</span><del>- else:
- values = (unicode(expression.fieldValue),)
</del><span class="cx">
</span><del>- # Escape special LDAP query characters
- values = [value.translate(LDAP_QUOTING_TABLE) for value in values]
- del value # Symbol used below; ensure non-reuse of data
</del><ins>+ if notOp:
+ valueOperand = LDAPOperand.OR.value
+ else:
+ valueOperand = LDAPOperand.AND.value
</ins><span class="cx">
</span><del>- # Compose an query using each of the LDAP attributes cooresponding to the
- # target field name.
</del><ins>+ queryStrings = [
+ matchType.queryString.format(
+ notOp=notOp, attribute=attribute, value=value,
+ )
+ for attribute, value in schema.attributes
+ ]
</ins><span class="cx">
</span><del>- if notOp:
- valueOperand = LDAPOperand.OR.value
</del><ins>+ return ldapQueryStringFromQueryStrings(valueOperand, queryStrings)
+
</ins><span class="cx"> else:
</span><del>- valueOperand = LDAPOperand.AND.value
</del><ins>+ try:
+ attributes = fieldNameToAttributesMap[fieldName]
+ except KeyError:
+ raise QueryNotSupportedError(
+ "Unmapped field name: {0}".format(expression.fieldName)
+ )
</ins><span class="cx">
</span><del>- queryStrings = [
- ldapQueryStringFromQueryStrings(
- valueOperand,
- [
- matchType.queryString.format(
- notOp=notOp, attribute=attribute, value=value
- )
- for value in values
- ]
- )
- for attribute in attributes
- ]
</del><ins>+ # Covert to unicode and escape special LDAP query characters
+ value = unicode(expression.fieldValue).translate(LDAP_QUOTING_TABLE)
</ins><span class="cx">
</span><del>- return ldapQueryStringFromQueryStrings(operand, queryStrings)
</del><ins>+ # Compose an query using each of the LDAP attributes cooresponding to
+ # the target field name.
</ins><span class="cx">
</span><ins>+ queryStrings = [
+ matchType.queryString.format(
+ notOp=notOp, attribute=attribute, value=value
+ )
+ for attribute in attributes
+ ]
</ins><span class="cx">
</span><ins>+ return ldapQueryStringFromQueryStrings(operand, queryStrings)
+
+ raise AssertionError("We shouldn't be here.")
+
+
</ins><span class="cx"> def ldapQueryStringFromCompoundExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> ):
</span><span class="cx"> """
</span><span class="cx"> Generates an LDAP query string from a compound expression.
</span><span class="lines">@@ -154,15 +163,14 @@
</span><span class="cx"> @param expression: A compound expression.
</span><span class="cx"> @type expression: L{MatchExpression}
</span><span class="cx">
</span><del>- @param fieldNameToAttributeMap: A mapping from field names to native LDAP
</del><ins>+ @param fieldNameToAttributesMap: A mapping from field names to native LDAP
</ins><span class="cx"> attribute names.
</span><del>- @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
</del><ins>+ @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
</ins><span class="cx"> of L{unicode} values.
</span><span class="cx">
</span><del>- @param recordTypeToObjectClassMap: A mapping from L{RecordType}s to native
- LDAP object class names.
- @type recordTypeToObjectClassMap: L{dict} with L{RecordType} keys and
- sequence of L{unicode} values.
</del><ins>+ @param recordTypeSchemas: Schema information for record types.
+ @type recordTypeSchemas: mapping from L{NamedConstant} to
+ L{RecordTypeSchema}
</ins><span class="cx">
</span><span class="cx"> @return: An LDAP query string.
</span><span class="cx"> @rtype: L{unicode}
</span><span class="lines">@@ -179,7 +187,7 @@
</span><span class="cx"> queryStrings = [
</span><span class="cx"> ldapQueryStringFromExpression(
</span><span class="cx"> subExpression,
</span><del>- fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx"> for subExpression in expression.expressions
</span><span class="cx"> ]
</span><span class="lines">@@ -188,7 +196,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def ldapQueryStringFromExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> ):
</span><span class="cx"> """
</span><span class="cx"> Converts an expression into an LDAP query string.
</span><span class="lines">@@ -196,15 +204,14 @@
</span><span class="cx"> @param expression: An expression.
</span><span class="cx"> @type expression: L{MatchExpression} or L{CompoundExpression}
</span><span class="cx">
</span><del>- @param fieldNameToAttributeMap: A mapping from field names to native LDAP
</del><ins>+ @param fieldNameToAttributesMap: A mapping from field names to native LDAP
</ins><span class="cx"> attribute names.
</span><del>- @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
</del><ins>+ @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
</ins><span class="cx"> of L{unicode} values.
</span><span class="cx">
</span><del>- @param recordTypeToObjectClassMap: A mapping from L{RecordType}s to native
- LDAP object class names.
- @type recordTypeToObjectClassMap: L{dict} with L{RecordType} keys and
- sequence of L{unicode} values.
</del><ins>+ @param recordTypeSchemas: Schema information for record types.
+ @type recordTypeSchemas: mapping from L{NamedConstant} to
+ L{RecordTypeSchema}
</ins><span class="cx">
</span><span class="cx"> @return: An LDAP query string.
</span><span class="cx"> @rtype: L{unicode}
</span><span class="lines">@@ -215,12 +222,12 @@
</span><span class="cx">
</span><span class="cx"> if isinstance(expression, MatchExpression):
</span><span class="cx"> return ldapQueryStringFromMatchExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> if isinstance(expression, CompoundExpression):
</span><span class="cx"> return ldapQueryStringFromCompoundExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributesMap, recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> raise QueryNotSupportedError(
</span></span></pre></div>
<a id="twexttrunktwextwholdaptesttest_servicepy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/who/ldap/test/test_service.py (12390 => 12391)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/who/ldap/test/test_service.py        2014-01-17 23:34:52 UTC (rev 12390)
+++ twext/trunk/twext/who/ldap/test/test_service.py        2014-01-18 03:10:38 UTC (rev 12391)
</span><span class="lines">@@ -33,7 +33,7 @@
</span><span class="cx">
</span><span class="cx"> from ...idirectory import QueryNotSupportedError, FieldName as BaseFieldName
</span><span class="cx"> from .._service import (
</span><del>- DEFAULT_FIELDNAME_ATTRIBUTE_MAP, DEFAULT_RECORDTYPE_OBJECTCLASS_MAP,
</del><ins>+ DEFAULT_FIELDNAME_ATTRIBUTE_MAP, DEFAULT_RECORDTYPE_SCHEMAS,
</ins><span class="cx"> LDAPBindAuthError,
</span><span class="cx"> DirectoryService, DirectoryRecord,
</span><span class="cx"> )
</span><span class="lines">@@ -85,7 +85,7 @@
</span><span class="cx"> return DirectoryService(
</span><span class="cx"> url=self.url,
</span><span class="cx"> baseDN=self.baseDN,
</span><del>- fieldNameToAttributeMap=TEST_FIELDNAME_MAP,
</del><ins>+ fieldNameToAttributesMap=TEST_FIELDNAME_MAP,
</ins><span class="cx"> **kwargs
</span><span class="cx"> )
</span><span class="cx">
</span><span class="lines">@@ -238,16 +238,22 @@
</span><span class="cx"> return unicode(obj)
</span><span class="cx">
</span><span class="cx"> def tuplify(record, fieldName):
</span><ins>+ fieldValue = record.fields[fieldName]
+
</ins><span class="cx"> if fieldName is BaseFieldName.recordType:
</span><del>- values = DEFAULT_RECORDTYPE_OBJECTCLASS_MAP[
- record.fields[fieldName]
- ]
</del><ins>+ schema = DEFAULT_RECORDTYPE_SCHEMAS[fieldValue]
+
+ return schema.attributes
+
</ins><span class="cx"> else:
</span><del>- values = (toUnicode(record.fields[fieldName]),)
</del><ins>+ value = toUnicode(fieldValue)
</ins><span class="cx">
</span><del>- for name in TEST_FIELDNAME_MAP.get(fieldName, fieldName.name):
- for value in values:
- yield (name, value)
</del><ins>+ return (
+ (name, value)
+ for name in TEST_FIELDNAME_MAP.get(
+ fieldName, (u"__" + fieldName.name,)
+ )
+ )
</ins><span class="cx">
</span><span class="cx"> for records in service.index[service.fieldName.uid].itervalues():
</span><span class="cx"> for record in records:
</span></span></pre></div>
<a id="twexttrunktwextwholdaptesttest_utilpy"></a>
<div class="modfile"><h4>Modified: twext/trunk/twext/who/ldap/test/test_util.py (12390 => 12391)</h4>
<pre class="diff"><span>
<span class="info">--- twext/trunk/twext/who/ldap/test/test_util.py        2014-01-17 23:34:52 UTC (rev 12390)
+++ twext/trunk/twext/who/ldap/test/test_util.py        2014-01-18 03:10:38 UTC (rev 12391)
</span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> CompoundExpression, Operand, MatchExpression, MatchType, MatchFlags
</span><span class="cx"> )
</span><span class="cx"> from .._constants import LDAPOperand
</span><del>-from .._service import DirectoryService
</del><ins>+from .._service import DirectoryService, RecordTypeSchema
</ins><span class="cx"> from .._util import (
</span><span class="cx"> ldapQueryStringFromQueryStrings,
</span><span class="cx"> ldapQueryStringFromMatchExpression,
</span><span class="lines">@@ -59,7 +59,7 @@
</span><span class="cx"> ])
</span><span class="cx">
</span><span class="cx">
</span><del>- def recordTypeMap(self, service):
</del><ins>+ def recordTypeSchemas(self, service):
</ins><span class="cx"> """
</span><span class="cx"> Create a mapping from record types to LDAP object class names.
</span><span class="cx"> The object class names returned here are not real LDAP object class
</span><span class="lines">@@ -67,7 +67,15 @@
</span><span class="cx"> connecting to LDAP.
</span><span class="cx"> """
</span><span class="cx"> return dict([
</span><del>- (c, (c.name,))
</del><ins>+ (
+ c,
+ RecordTypeSchema(
+ relativeDN=NotImplemented, # Don't expect this to be used
+ attributes=(
+ (u"recordTypeAttribute", c.name),
+ )
+ )
+ )
</ins><span class="cx"> for c in service.recordType.iterconstants()
</span><span class="cx"> ])
</span><span class="cx">
</span><span class="lines">@@ -127,7 +135,7 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"({attribute}{expected})".format(
</span><span class="cx"> attribute=u"shortNames", expected=expected
</span><span class="lines">@@ -147,7 +155,7 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"(!{attribute}=xyzzy)".format(
</span><span class="cx"> attribute=u"shortNames",
</span><span class="lines">@@ -168,7 +176,7 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"???????({attribute}=xyzzy)".format(
</span><span class="cx"> attribute=u"shortNames",
</span><span class="lines">@@ -193,7 +201,7 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"({attribute}={expected})".format(
</span><span class="cx"> attribute=u"fullNames",
</span><span class="lines">@@ -219,7 +227,7 @@
</span><span class="cx"> QueryNotSupportedError,
</span><span class="cx"> ldapQueryStringFromMatchExpression,
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -238,7 +246,7 @@
</span><span class="cx"> QueryNotSupportedError,
</span><span class="cx"> ldapQueryStringFromMatchExpression,
</span><span class="cx"> expression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -257,7 +265,8 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><del>- expression, fieldNameToAttributeMap, self.recordTypeMap(service)
</del><ins>+ expression,
+ fieldNameToAttributeMap, self.recordTypeSchemas(service)
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> self.assertEquals(queryString, expected)
</span><span class="lines">@@ -305,17 +314,37 @@
</span><span class="cx">
</span><span class="cx"> fieldNameToAttributeMap = self.fieldNameMap(service)
</span><span class="cx">
</span><del>- recordTypeToObjectClassMap = {
- service.recordType.user: (u"person", u"account"),
</del><ins>+ recordTypeAttr = fieldNameToAttributeMap[recordTypeField][0]
+ type1 = u"person"
+ type2 = u"coolPerson"
+
+ statusAttr = u"accountStatus"
+ status1 = u"active"
+
+ recordTypeSchemas = {
+ service.recordType.user: RecordTypeSchema(
+ relativeDN=NotImplemented, # Don't expect this to be used.
+ attributes=(
+ (recordTypeAttr, type1),
+ (recordTypeAttr, type2),
+ (statusAttr, status1),
+ )
+ )
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> queryString = ldapQueryStringFromMatchExpression(
</span><del>- expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
</del><ins>+ expression, fieldNameToAttributeMap, recordTypeSchemas
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> self.assertEquals(
</span><span class="cx"> queryString,
</span><del>- expected.format(attr=fieldNameToAttributeMap[recordTypeField][0])
</del><ins>+ expected.format(
+ recordType=recordTypeAttr,
+ type1=type1,
+ type2=type2,
+ accountStatus=statusAttr,
+ status1=status1,
+ )
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -326,7 +355,11 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> # We want a match for both values.
</span><del>- expected = u"(&({attr}=person)({attr}=account))"
</del><ins>+ expected = (
+ u"(&({recordType}={type1})"
+ u"({recordType}={type2})"
+ u"({accountStatus}={status1}))"
+ )
</ins><span class="cx">
</span><span class="cx"> return self._test_queryStringFromMatchExpression_multiRecordType(
</span><span class="cx"> MatchFlags.none, expected
</span><span class="lines">@@ -339,7 +372,11 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> # We want a NOT match for either value.
</span><del>- expected = u"(|(!{attr}=person)(!{attr}=account))"
</del><ins>+ expected = (
+ u"(|(!{recordType}={type1})"
+ u"(!{recordType}={type2})"
+ u"(!{accountStatus}={status1}))"
+ )
</ins><span class="cx">
</span><span class="cx"> return self._test_queryStringFromMatchExpression_multiRecordType(
</span><span class="cx"> MatchFlags.NOT, expected
</span><span class="lines">@@ -370,12 +407,13 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = queryFunction(
</span><span class="cx"> compoundExpression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"{match}".format(
</span><span class="cx"> match=ldapQueryStringFromMatchExpression(
</span><span class="cx"> matchExpression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service),
+ self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> )
</span><span class="cx"> self.assertEquals(queryString, expected)
</span><span class="lines">@@ -404,17 +442,19 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = queryFunction(
</span><span class="cx"> compoundExpression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = u"({op}{match1}{match2})".format(
</span><span class="cx"> op=token,
</span><span class="cx"> match1=ldapQueryStringFromMatchExpression(
</span><span class="cx"> matchExpression1,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service),
+ self.recordTypeSchemas(service),
</ins><span class="cx"> ),
</span><span class="cx"> match2=ldapQueryStringFromMatchExpression(
</span><span class="cx"> matchExpression2,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service),
+ self.recordTypeSchemas(service),
</ins><span class="cx"> ),
</span><span class="cx"> )
</span><span class="cx"> self.assertEquals(queryString, expected)
</span><span class="lines">@@ -431,11 +471,11 @@
</span><span class="cx"> )
</span><span class="cx"> queryString = ldapQueryStringFromExpression(
</span><span class="cx"> matchExpression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> expected = ldapQueryStringFromMatchExpression(
</span><span class="cx"> matchExpression,
</span><del>- self.fieldNameMap(service), self.recordTypeMap(service),
</del><ins>+ self.fieldNameMap(service), self.recordTypeSchemas(service),
</ins><span class="cx"> )
</span><span class="cx"> self.assertEquals(queryString, expected)
</span><span class="cx">
</span><span class="lines">@@ -461,5 +501,6 @@
</span><span class="cx"> self.assertRaises(
</span><span class="cx"> QueryNotSupportedError,
</span><span class="cx"> ldapQueryStringFromExpression,
</span><del>- object(), self.fieldNameMap(service), self.recordTypeMap(service)
</del><ins>+ object(),
+ self.fieldNameMap(service), self.recordTypeSchemas(service)
</ins><span class="cx"> )
</span></span></pre>
</div>
</div>
</body>
</html>