[CalendarServer-changes] [12391] twext/trunk/twext/who/ldap

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:17:40 PDT 2014


Revision: 12391
          http://trac.calendarserver.org//changeset/12391
Author:   wsanchez at apple.com
Date:     2014-01-17 19:10:38 -0800 (Fri, 17 Jan 2014)
Log Message:
-----------
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.

Modified Paths:
--------------
    twext/trunk/twext/who/ldap/_constants.py
    twext/trunk/twext/who/ldap/_service.py
    twext/trunk/twext/who/ldap/_util.py
    twext/trunk/twext/who/ldap/test/test_service.py
    twext/trunk/twext/who/ldap/test/test_util.py

Modified: twext/trunk/twext/who/ldap/_constants.py
===================================================================
--- 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)
@@ -125,6 +125,7 @@
     """
     LDAP match flags.
     """
+    none = ValueConstant(u"")
     NOT = ValueConstant(u"!")
 
 

Modified: twext/trunk/twext/who/ldap/_service.py
===================================================================
--- 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)
@@ -50,25 +50,6 @@
 
 
 
-# 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,),
-})
-
-
-
 #
 # Exceptions
 #
@@ -124,6 +105,77 @@
 
 
 #
+# 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,
+            ),
+        ),
+    ),
+
+})
+
+
+
+#
 # Directory Service
 #
 
@@ -148,8 +200,8 @@
         tlsCACertificateFile=None,
         tlsCACertificateDirectory=None,
         useTLS=False,
-        fieldNameToAttributeMap=DEFAULT_FIELDNAME_ATTRIBUTE_MAP,
-        recordTypeToObjectClassMap=DEFAULT_RECORDTYPE_OBJECTCLASS_MAP,
+        fieldNameToAttributesMap=DEFAULT_FIELDNAME_ATTRIBUTE_MAP,
+        recordTypeSchemas=DEFAULT_RECORDTYPE_SCHEMAS,
         uidField=BaseFieldName.uid,
         _debug=False,
     ):
@@ -176,15 +228,14 @@
         @param useTLS: Enable the use of TLS.
         @type useTLS: L{bool}
 
-        @param fieldNameToAttributeMap: A mapping of field names to LDAP
+        @param fieldNameToAttributesMap: A mapping of field names to LDAP
             attribute names.
-        @type fieldNameToAttributeMap: mapping with L{NamedConstant} keys and
+        @type fieldNameToAttributesMap: mapping with L{NamedConstant} keys and
             sequence of L{unicode} values
 
-        @param recordTypeToObjectClassMap: A mapping of record types to LDAP
-            object classes.
-        @type recordTypeToObjectClassMap: mapping with L{NamedConstant} keys
-            and sequence of L{unicode} values
+        @param recordTypeSchemas: Schema information for record types.
+        @type recordTypeSchemas: mapping from L{NamedConstant} to
+            L{RecordTypeSchema}
         """
 
         self.url = url
@@ -209,30 +260,14 @@
         else:
             self._debug = None
 
-        def reverseDict(sourceName, source):
-            new = {}
+        if self.fieldName.recordType in fieldNameToAttributesMap:
+            raise TypeError("Record type field may not be mapped")
 
-            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
+        self._fieldNameToAttributesMap = fieldNameToAttributesMap
         self._attributeToFieldNameMap = reverseDict(
-            "Field name", fieldNameToAttributeMap
+            "Field name", fieldNameToAttributesMap
         )
-
-        self._recordTypeToObjectClassMap = recordTypeToObjectClassMap
-        self._objectClassToRecordTypeMap = reverseDict(
-            "Record type", recordTypeToObjectClassMap
-        )
-
+        self._recordTypeSchemas = recordTypeSchemas
         self._uidField = uidField
 
 
@@ -312,69 +347,68 @@
         self.log.debug("Performing LDAP query: {query}", query=queryString)
 
         reply = connection.search_s(
-            self._baseDN, ldap.SCOPE_SUBTREE, queryString  # attrs
+            self._baseDN, ldap.SCOPE_SUBTREE, queryString  # FIXME: attrs
         )
 
         records = []
 
-        # Note: self._uidField is the name of the field in
-        # self._fieldNameToAttributeMap that tells us which LDAP attribute
+        # self._uidField is the name of the field in
+        # self._fieldNameToAttributesMap that tells us which LDAP attribute
         # we are using to determine the UID of the record.
 
-        uidField = self.fieldName.uid
-        uidAttribute = self._fieldNameToAttributeMap[self._uidField][0]
+        uidAttribute = self._fieldNameToAttributesMap[self._uidField][0]
 
-        recordTypeField = self.fieldName.recordType
-        recordTypeAttributes = (
-            self._fieldNameToAttributeMap[self.fieldName.recordType]
-        )
+        # recordTypeAttributes = set(chain(*[
+        #     info[u"attributes"].iterkeys()
+        #     for info in self._recordTypeInfo.itervalues()
+        # ]))
 
         for dn, recordData in reply:
 
-            # Attributes used to determine the record type are required, since
-            # record type is very much required.
+            # Fetch the UID
 
-            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
-
             try:
-                fields[uidField] = recordData[uidAttribute]
+                uid = recordData[uidAttribute]
             except KeyError:
                 self.log.debug(
-                    "Ignoring LDAP record data with no UID attribute "
-                    "{source._uidField!r}: {recordData!r}",
+                    "Ignoring LDAP record data; no UID attribute "
+                    "({source._uidField}): {recordData!r}",
                     recordData=recordData
                 )
                 continue
 
+            # Determine the record type
 
-            # Coerce data to the correct type
+            recordType = recordTypeForRecordData(
+                self._recordTypeSchemas, recordData
+            )
 
-            for fieldName, value in fields.iteritems():
+            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,
+                    )
+
                 valueType = self.fieldName.valueType(fieldName)
 
-                if fieldName is recordTypeField:
-                    value = self._objectClassToRecordTypeMap[value]
-                elif valueType in (unicode, UUID):
-                    value = valueType(value)
+                if valueType in (unicode, UUID):
+                    fields[fieldName] = valueType(value)
+
                 else:
                     raise LDAPConfigurationError(
                         "Unknown value type {0} for field {1}".format(
@@ -382,8 +416,11 @@
                         )
                     )
 
-                fields[fieldName] = value
+            # Set record type and uid fields
 
+            fields[self.fieldName.recordType] = recordType
+            fields[self.fieldName.uid] = uid
+
             # Make a record object from fields.
 
             record = DirectoryRecord(self, fields)
@@ -398,7 +435,7 @@
         if isinstance(expression, MatchExpression):
             queryString = ldapQueryStringFromMatchExpression(
                 expression,
-                self._fieldNameToAttributeMap, self._recordTypeToObjectClassMap
+                self._fieldNameToAttributesMap, self._recordTypeSchemas
             )
             return self._recordsFromQueryString(queryString)
 
@@ -413,7 +450,7 @@
 
         queryString = ldapQueryStringFromCompoundExpression(
             expression,
-            self._fieldNameToAttributeMap, self._recordTypeToObjectClassMap
+            self._fieldNameToAttributesMap, self._recordTypeSchemas
         )
         return self._recordsFromQueryString(queryString)
 
@@ -423,3 +460,42 @@
     """
     LDAP directory record.
     """
+
+
+
+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

Modified: twext/trunk/twext/who/ldap/_util.py
===================================================================
--- 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)
@@ -33,7 +33,7 @@
     @type operand: L{unicode}
 
     @param queryStrings: LDAP query strings.
-    @type queryStrings: iterable of L{unicode}
+    @type queryStrings: sequence of L{unicode}
     """
     if len(queryStrings) == 1:
         return queryStrings[0]
@@ -51,7 +51,7 @@
 
 
 def ldapQueryStringFromMatchExpression(
-    expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+    expression, fieldNameToAttributesMap, recordTypeSchemas
 ):
     """
     Generates an LDAP query string from a match expression.
@@ -59,15 +59,14 @@
     @param expression: A match expression.
     @type expression: L{MatchExpression}
 
-    @param fieldNameToAttributeMap: A mapping from field names to native LDAP
+    @param fieldNameToAttributesMap: A mapping from field names to native LDAP
         attribute names.
-    @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
+    @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
         of L{unicode} values.
 
-    @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.
+    @param recordTypeSchemas: Schema information for record types.
+    @type recordTypeSchemas: mapping from L{NamedConstant} to
+        L{RecordTypeSchema}
 
     @return: An LDAP query string.
     @rtype: L{unicode}
@@ -89,7 +88,7 @@
         notOp = LDAPMatchFlags.NOT.value
         operand = LDAPOperand.AND.value
     else:
-        notOp = u""
+        notOp = LDAPMatchFlags.none.value
         operand = LDAPOperand.OR.value
 
     # FIXME: It doesn't look like LDAP queries can be case sensitive.
@@ -100,53 +99,63 @@
     #     raise NotImplementedError("Need to handle case sensitive")
 
     fieldName = expression.fieldName
-    try:
-        attributes = fieldNameToAttributeMap[fieldName]
-    except KeyError:
-        raise QueryNotSupportedError(
-            "Unmapped field name: {0}".format(expression.fieldName)
-        )
 
     if fieldName is FieldName.recordType:
+        if matchType is not LDAPMatchType.equals:
+            raise QueryNotSupportedError(
+                "Match type for record type must be equals, not {0!r}"
+                .format(expression.matchType)
+            )
+
         try:
-            values = recordTypeToObjectClassMap[expression.fieldValue]
+            schema = recordTypeSchemas[expression.fieldValue]
         except KeyError:
             raise QueryNotSupportedError(
                 "Unmapped record type: {0}".format(expression.fieldValue)
             )
-    else:
-        values = (unicode(expression.fieldValue),)
 
-    # 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
+        if notOp:
+            valueOperand = LDAPOperand.OR.value
+        else:
+            valueOperand = LDAPOperand.AND.value
 
-    # Compose an query using each of the LDAP attributes cooresponding to the
-    # target field name.
+        queryStrings = [
+            matchType.queryString.format(
+                notOp=notOp, attribute=attribute, value=value,
+            )
+            for attribute, value in schema.attributes
+        ]
 
-    if notOp:
-        valueOperand = LDAPOperand.OR.value
+        return ldapQueryStringFromQueryStrings(valueOperand, queryStrings)
+
     else:
-        valueOperand = LDAPOperand.AND.value
+        try:
+            attributes = fieldNameToAttributesMap[fieldName]
+        except KeyError:
+            raise QueryNotSupportedError(
+                "Unmapped field name: {0}".format(expression.fieldName)
+            )
 
-    queryStrings = [
-        ldapQueryStringFromQueryStrings(
-            valueOperand,
-            [
-                matchType.queryString.format(
-                    notOp=notOp, attribute=attribute, value=value
-                )
-                for value in values
-            ]
-        )
-        for attribute in attributes
-    ]
+        # Covert to unicode and escape special LDAP query characters
+        value = unicode(expression.fieldValue).translate(LDAP_QUOTING_TABLE)
 
-    return ldapQueryStringFromQueryStrings(operand, queryStrings)
+        # Compose an query using each of the LDAP attributes cooresponding to
+        # the target field name.
 
+        queryStrings = [
+            matchType.queryString.format(
+                notOp=notOp, attribute=attribute, value=value
+            )
+            for attribute in attributes
+        ]
 
+        return ldapQueryStringFromQueryStrings(operand, queryStrings)
+
+    raise AssertionError("We shouldn't be here.")
+
+
 def ldapQueryStringFromCompoundExpression(
-    expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+    expression, fieldNameToAttributesMap, recordTypeSchemas
 ):
     """
     Generates an LDAP query string from a compound expression.
@@ -154,15 +163,14 @@
     @param expression: A compound expression.
     @type expression: L{MatchExpression}
 
-    @param fieldNameToAttributeMap: A mapping from field names to native LDAP
+    @param fieldNameToAttributesMap: A mapping from field names to native LDAP
         attribute names.
-    @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
+    @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
         of L{unicode} values.
 
-    @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.
+    @param recordTypeSchemas: Schema information for record types.
+    @type recordTypeSchemas: mapping from L{NamedConstant} to
+        L{RecordTypeSchema}
 
     @return: An LDAP query string.
     @rtype: L{unicode}
@@ -179,7 +187,7 @@
     queryStrings = [
         ldapQueryStringFromExpression(
             subExpression,
-            fieldNameToAttributeMap, recordTypeToObjectClassMap
+            fieldNameToAttributesMap, recordTypeSchemas
         )
         for subExpression in expression.expressions
     ]
@@ -188,7 +196,7 @@
 
 
 def ldapQueryStringFromExpression(
-    expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+    expression, fieldNameToAttributesMap, recordTypeSchemas
 ):
     """
     Converts an expression into an LDAP query string.
@@ -196,15 +204,14 @@
     @param expression: An expression.
     @type expression: L{MatchExpression} or L{CompoundExpression}
 
-    @param fieldNameToAttributeMap: A mapping from field names to native LDAP
+    @param fieldNameToAttributesMap: A mapping from field names to native LDAP
         attribute names.
-    @type fieldNameToAttributeMap: L{dict} with L{FieldName} keys and sequence
+    @type fieldNameToAttributesMap: L{dict} with L{FieldName} keys and sequence
         of L{unicode} values.
 
-    @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.
+    @param recordTypeSchemas: Schema information for record types.
+    @type recordTypeSchemas: mapping from L{NamedConstant} to
+        L{RecordTypeSchema}
 
     @return: An LDAP query string.
     @rtype: L{unicode}
@@ -215,12 +222,12 @@
 
     if isinstance(expression, MatchExpression):
         return ldapQueryStringFromMatchExpression(
-            expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+            expression, fieldNameToAttributesMap, recordTypeSchemas
         )
 
     if isinstance(expression, CompoundExpression):
         return ldapQueryStringFromCompoundExpression(
-            expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+            expression, fieldNameToAttributesMap, recordTypeSchemas
         )
 
     raise QueryNotSupportedError(

Modified: twext/trunk/twext/who/ldap/test/test_service.py
===================================================================
--- 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)
@@ -33,7 +33,7 @@
 
 from ...idirectory import QueryNotSupportedError, FieldName as BaseFieldName
 from .._service import (
-    DEFAULT_FIELDNAME_ATTRIBUTE_MAP, DEFAULT_RECORDTYPE_OBJECTCLASS_MAP,
+    DEFAULT_FIELDNAME_ATTRIBUTE_MAP, DEFAULT_RECORDTYPE_SCHEMAS,
     LDAPBindAuthError,
     DirectoryService, DirectoryRecord,
 )
@@ -85,7 +85,7 @@
         return DirectoryService(
             url=self.url,
             baseDN=self.baseDN,
-            fieldNameToAttributeMap=TEST_FIELDNAME_MAP,
+            fieldNameToAttributesMap=TEST_FIELDNAME_MAP,
             **kwargs
         )
 
@@ -238,16 +238,22 @@
         return unicode(obj)
 
     def tuplify(record, fieldName):
+        fieldValue = record.fields[fieldName]
+
         if fieldName is BaseFieldName.recordType:
-            values = DEFAULT_RECORDTYPE_OBJECTCLASS_MAP[
-                record.fields[fieldName]
-            ]
+            schema = DEFAULT_RECORDTYPE_SCHEMAS[fieldValue]
+
+            return schema.attributes
+
         else:
-            values = (toUnicode(record.fields[fieldName]),)
+            value = toUnicode(fieldValue)
 
-        for name in TEST_FIELDNAME_MAP.get(fieldName, fieldName.name):
-            for value in values:
-                yield (name, value)
+            return (
+                (name, value)
+                for name in TEST_FIELDNAME_MAP.get(
+                    fieldName, (u"__" + fieldName.name,)
+                )
+            )
 
     for records in service.index[service.fieldName.uid].itervalues():
         for record in records:

Modified: twext/trunk/twext/who/ldap/test/test_util.py
===================================================================
--- 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)
@@ -25,7 +25,7 @@
     CompoundExpression, Operand, MatchExpression, MatchType, MatchFlags
 )
 from .._constants import LDAPOperand
-from .._service import DirectoryService
+from .._service import DirectoryService, RecordTypeSchema
 from .._util import (
     ldapQueryStringFromQueryStrings,
     ldapQueryStringFromMatchExpression,
@@ -59,7 +59,7 @@
         ])
 
 
-    def recordTypeMap(self, service):
+    def recordTypeSchemas(self, service):
         """
         Create a mapping from record types to LDAP object class names.
         The object class names returned here are not real LDAP object class
@@ -67,7 +67,15 @@
         connecting to LDAP.
         """
         return dict([
-            (c, (c.name,))
+            (
+                c,
+                RecordTypeSchema(
+                    relativeDN=NotImplemented,  # Don't expect this to be used
+                    attributes=(
+                        (u"recordTypeAttribute", c.name),
+                    )
+                )
+            )
             for c in service.recordType.iterconstants()
         ])
 
@@ -127,7 +135,7 @@
             )
             queryString = ldapQueryStringFromMatchExpression(
                 expression,
-                self.fieldNameMap(service), self.recordTypeMap(service),
+                self.fieldNameMap(service), self.recordTypeSchemas(service),
             )
             expected = u"({attribute}{expected})".format(
                 attribute=u"shortNames", expected=expected
@@ -147,7 +155,7 @@
         )
         queryString = ldapQueryStringFromMatchExpression(
             expression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
         expected = u"(!{attribute}=xyzzy)".format(
             attribute=u"shortNames",
@@ -168,7 +176,7 @@
         )
         queryString = ldapQueryStringFromMatchExpression(
             expression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
         expected = u"???????({attribute}=xyzzy)".format(
             attribute=u"shortNames",
@@ -193,7 +201,7 @@
         )
         queryString = ldapQueryStringFromMatchExpression(
             expression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
         expected = u"({attribute}={expected})".format(
             attribute=u"fullNames",
@@ -219,7 +227,7 @@
             QueryNotSupportedError,
             ldapQueryStringFromMatchExpression,
             expression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
 
 
@@ -238,7 +246,7 @@
             QueryNotSupportedError,
             ldapQueryStringFromMatchExpression,
             expression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
 
 
@@ -257,7 +265,8 @@
         }
 
         queryString = ldapQueryStringFromMatchExpression(
-            expression, fieldNameToAttributeMap, self.recordTypeMap(service)
+            expression,
+            fieldNameToAttributeMap, self.recordTypeSchemas(service)
         )
 
         self.assertEquals(queryString, expected)
@@ -305,17 +314,37 @@
 
         fieldNameToAttributeMap = self.fieldNameMap(service)
 
-        recordTypeToObjectClassMap = {
-            service.recordType.user: (u"person", u"account"),
+        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),
+                )
+            )
         }
 
         queryString = ldapQueryStringFromMatchExpression(
-            expression, fieldNameToAttributeMap, recordTypeToObjectClassMap
+            expression, fieldNameToAttributeMap, recordTypeSchemas
         )
 
         self.assertEquals(
             queryString,
-            expected.format(attr=fieldNameToAttributeMap[recordTypeField][0])
+            expected.format(
+                recordType=recordTypeAttr,
+                type1=type1,
+                type2=type2,
+                accountStatus=statusAttr,
+                status1=status1,
+            )
         )
 
 
@@ -326,7 +355,11 @@
         """
 
         # We want a match for both values.
-        expected = u"(&({attr}=person)({attr}=account))"
+        expected = (
+            u"(&({recordType}={type1})"
+            u"({recordType}={type2})"
+            u"({accountStatus}={status1}))"
+        )
 
         return self._test_queryStringFromMatchExpression_multiRecordType(
             MatchFlags.none, expected
@@ -339,7 +372,11 @@
         """
 
         # We want a NOT match for either value.
-        expected = u"(|(!{attr}=person)(!{attr}=account))"
+        expected = (
+            u"(|(!{recordType}={type1})"
+            u"(!{recordType}={type2})"
+            u"(!{accountStatus}={status1}))"
+        )
 
         return self._test_queryStringFromMatchExpression_multiRecordType(
             MatchFlags.NOT, expected
@@ -370,12 +407,13 @@
             )
             queryString = queryFunction(
                 compoundExpression,
-                self.fieldNameMap(service), self.recordTypeMap(service),
+                self.fieldNameMap(service), self.recordTypeSchemas(service),
             )
             expected = u"{match}".format(
                 match=ldapQueryStringFromMatchExpression(
                     matchExpression,
-                    self.fieldNameMap(service), self.recordTypeMap(service),
+                    self.fieldNameMap(service),
+                    self.recordTypeSchemas(service),
                 )
             )
             self.assertEquals(queryString, expected)
@@ -404,17 +442,19 @@
             )
             queryString = queryFunction(
                 compoundExpression,
-                self.fieldNameMap(service), self.recordTypeMap(service),
+                self.fieldNameMap(service), self.recordTypeSchemas(service),
             )
             expected = u"({op}{match1}{match2})".format(
                 op=token,
                 match1=ldapQueryStringFromMatchExpression(
                     matchExpression1,
-                    self.fieldNameMap(service), self.recordTypeMap(service),
+                    self.fieldNameMap(service),
+                    self.recordTypeSchemas(service),
                 ),
                 match2=ldapQueryStringFromMatchExpression(
                     matchExpression2,
-                    self.fieldNameMap(service), self.recordTypeMap(service),
+                    self.fieldNameMap(service),
+                    self.recordTypeSchemas(service),
                 ),
             )
             self.assertEquals(queryString, expected)
@@ -431,11 +471,11 @@
         )
         queryString = ldapQueryStringFromExpression(
             matchExpression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
         expected = ldapQueryStringFromMatchExpression(
             matchExpression,
-            self.fieldNameMap(service), self.recordTypeMap(service),
+            self.fieldNameMap(service), self.recordTypeSchemas(service),
         )
         self.assertEquals(queryString, expected)
 
@@ -461,5 +501,6 @@
         self.assertRaises(
             QueryNotSupportedError,
             ldapQueryStringFromExpression,
-            object(), self.fieldNameMap(service), self.recordTypeMap(service)
+            object(),
+            self.fieldNameMap(service), self.recordTypeSchemas(service)
         )
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/fd0d5298/attachment.html>


More information about the calendarserver-changes mailing list