[CalendarServer-changes] [12279] twext/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 12 11:23:24 PDT 2014
Revision: 12279
http://trac.calendarserver.org//changeset/12279
Author: wsanchez at apple.com
Date: 2014-01-09 19:39:29 -0800 (Thu, 09 Jan 2014)
Log Message:
-----------
More LDAP
Modified Paths:
--------------
twext/trunk/pyflakes
twext/trunk/setup.py
twext/trunk/twext/who/opendirectory/__init__.py
twext/trunk/twext/who/opendirectory/_constants.py
twext/trunk/twext/who/opendirectory/_service.py
Added Paths:
-----------
twext/trunk/twext/who/ldap/
twext/trunk/twext/who/ldap/__init__.py
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/
twext/trunk/twext/who/ldap/test/__init__.py
twext/trunk/twext/who/ldap/test/test_service.py
twext/trunk/twext/who/ldap/test/test_util.py
Removed Paths:
-------------
twext/trunk/twext/who/ldap.py
Modified: twext/trunk/pyflakes
===================================================================
--- twext/trunk/pyflakes 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/pyflakes 2014-01-10 03:39:29 UTC (rev 12279)
@@ -23,5 +23,5 @@
. "${wd}/develop";
-pip install pyflakes --target="${dev_libdir}" > "${dev_root}/pip_pyflakes.log";
+pip install pyflakes --no-use-wheel --upgrade --target="${dev_libdir}" > "${dev_root}/pip_pyflakes.log" || true;
exec "${python}" -m pyflakes "$@";
Modified: twext/trunk/setup.py
===================================================================
--- twext/trunk/setup.py 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/setup.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -116,8 +116,9 @@
]
install_requirements = [
+ "twisted>=13.2.0",
"sqlparse==0.1.2",
- "twisted>=13.2.0",
+ "python-ldap>=2.4.13",
]
Added: twext/trunk/twext/who/ldap/__init__.py
===================================================================
--- twext/trunk/twext/who/ldap/__init__.py (rev 0)
+++ twext/trunk/twext/who/ldap/__init__.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,34 @@
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+__all__ = [
+ "LDAPError",
+ "LDAPConnectionError",
+ # "LDAPQueryError",
+ # "LDAPDataError",
+ "DirectoryService",
+]
+
+
+try:
+ from ._service import (
+ LDAPError, LDAPConnectionError, # LDAPQueryError, LDAPDataError,
+ DirectoryService,
+ )
+except ImportError:
+ raise
+ LDAPError = LDAPConnectionError = None
+ # LDAPQueryError = LDAPDataError = None
Added: twext/trunk/twext/who/ldap/_constants.py
===================================================================
--- twext/trunk/twext/who/ldap/_constants.py (rev 0)
+++ twext/trunk/twext/who/ldap/_constants.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,99 @@
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+LDAP constants.
+"""
+
+from twisted.python.constants import (
+ Names, NamedConstant, # Values, ValueConstant
+)
+
+from ..expression import MatchType
+
+
+
+class LDAPMatchType(Names):
+ """
+ Constants for native LDAP match types.
+
+ @cvar any: Attribute has any value.
+ @cvar equals: Attribute equals value.
+ @cvar startsWith: Attribute starts with value.
+ @cvar endsWith: Attribute ends with value.
+ @cvar contains: Attribute contains value.
+ @cvar lessThan: Attribute is less than value.
+ @cvar greaterThan: Attribute is greater than value.
+ @cvar lessThanOrEqualTo: Attribute is less than or equal to value.
+ @cvar greaterThanOrEqualTo: Attribute is greater than or equal to value.
+
+ """
+
+ any = NamedConstant()
+ any.queryString = u"({notOp}{attribute}=*)"
+
+ equals = NamedConstant()
+ equals.matchType = MatchType.equals
+ equals.queryString = u"({notOp}{attribute}={value})"
+
+ startsWith = NamedConstant()
+ startsWith.matchType = MatchType.startsWith
+ startsWith.queryString = u"({notOp}{attribute}={value}*)"
+
+ endsWith = NamedConstant()
+ endsWith.matchType = MatchType.endsWith
+ endsWith.queryString = u"({notOp}{attribute}=*{value})"
+
+ contains = NamedConstant()
+ contains.matchType = MatchType.contains
+ contains.queryString = u"({notOp}{attribute}=*{value}*)"
+
+ lessThan = NamedConstant()
+ lessThan.matchType = MatchType.lessThan
+ lessThan.queryString = u"({notOp}{attribute}<{value})"
+
+ greaterThan = NamedConstant()
+ greaterThan.matchType = MatchType.greaterThan
+ greaterThan.queryString = u"({notOp}{attribute}>{value})"
+
+ lessThanOrEqualTo = NamedConstant()
+ lessThanOrEqualTo.matchType = MatchType.lessThanOrEqualTo
+ lessThanOrEqualTo.queryString = u"({notOp}{attribute}<={value})"
+
+ greaterThanOrEqualTo = NamedConstant()
+ greaterThanOrEqualTo.matchType = MatchType.greaterThanOrEqualTo
+ greaterThanOrEqualTo.queryString = u"({notOp}{attribute}>={value})"
+
+
+ @classmethod
+ def fromMatchType(cls, matchType):
+ """
+ Look up an L{LDAPMatchType} from a L{MatchType}.
+
+ @param matchType: A match type.
+ @type matchType: L{MatchType}
+
+ @return: The cooresponding LDAP match type.
+ @rtype: L{LDAPMatchType}
+ """
+ if not hasattr(cls, "_matchTypeByMatchType"):
+ cls._matchTypeByMatchType = dict((
+ (matchType.matchType, matchType)
+ for matchType in cls.iterconstants()
+ if hasattr(matchType, "matchType")
+ ))
+
+ return cls._matchTypeByMatchType.get(matchType, None)
Copied: twext/trunk/twext/who/ldap/_service.py (from rev 12278, twext/trunk/twext/who/ldap.py)
===================================================================
--- twext/trunk/twext/who/ldap/_service.py (rev 0)
+++ twext/trunk/twext/who/ldap/_service.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,201 @@
+# -*- test-case-name: twext.who.ldap.test.test_service -*-
+##
+# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from __future__ import print_function
+
+"""
+LDAP directory service implementation.
+"""
+
+import ldap
+
+# from zope.interface import implementer
+
+# from twisted.internet.defer import succeed, fail
+from twisted.cred.credentials import IUsernamePassword
+# from twisted.web.guard import DigestCredentialFactory
+
+from twext.python.log import Logger
+
+from ..idirectory import (
+ DirectoryServiceError, DirectoryAvailabilityError,
+ # InvalidDirectoryRecordError, QueryNotSupportedError,
+ # FieldName as BaseFieldName,
+ RecordType as BaseRecordType,
+ # IPlaintextPasswordVerifier, IHTTPDigestVerifier,
+)
+from ..directory import (
+ DirectoryService as BaseDirectoryService,
+ DirectoryRecord as BaseDirectoryRecord,
+)
+# from ..expression import (
+# CompoundExpression, Operand,
+# MatchExpression, MatchFlags,
+# )
+from ..util import (
+ # iterFlags,
+ ConstantsContainer,
+)
+# from ._constants import LDAP_QUOTING_TABLE
+
+
+
+#
+# Exceptions
+#
+
+class LDAPError(DirectoryServiceError):
+ """
+ LDAP error.
+ """
+
+ def __init__(self, message, ldapError=None):
+ super(LDAPError, self).__init__(message)
+ self.ldapError = ldapError
+
+
+
+class LDAPConnectionError(DirectoryAvailabilityError):
+ """
+ LDAP connection error.
+ """
+
+ def __init__(self, message, ldapError=None):
+ super(LDAPConnectionError, self).__init__(message)
+ self.ldapError = ldapError
+
+
+
+# class LDAPQueryError(LDAPError):
+# """
+# LDAP query error.
+# """
+
+
+
+# class LDAPDataError(LDAPError):
+# """
+# LDAP data error.
+# """
+
+
+
+#
+# Directory Service
+#
+
+class DirectoryService(BaseDirectoryService):
+ """
+ LDAP directory service.
+ """
+
+ log = Logger()
+
+ recordType = ConstantsContainer((
+ BaseRecordType.user, BaseRecordType.group,
+ ))
+
+
+ def __init__(
+ self,
+ url="ldap://localhost/",
+ credentials=None,
+ tlsCACertificateFile=None,
+ tlsCACertificateDirectory=None,
+ useTLS=False,
+ ):
+ self._url = url
+ self.credentials = credentials
+ self._tlsCACertificateFile = tlsCACertificateFile
+ self._tlsCACertificateDirectory = tlsCACertificateDirectory
+ self._useTLS = useTLS,
+
+
+ @property
+ def realmName(self):
+ return u"{self.url}".format(self=self)
+
+
+ @property
+ def connection(self):
+ """
+ Get the underlying LDAP connection.
+ """
+ self._connect()
+ return self._connection
+
+
+ def _connect(self):
+ """
+ Connect to the directory server.
+
+ @raises: L{LDAPConnectionError} if unable to connect.
+ """
+ if not hasattr(self, "_connection"):
+ self.log.info("Connecting to LDAP at {source.url}")
+ connection = ldap.initialize(self._url)
+ # FIXME: Use trace_file option to wire up debug logging when
+ # Twisted adopts the new logging stuff.
+
+ def valueFor(constant):
+ if constant is None:
+ return None
+ else:
+ return constant.value
+
+ for option, value in (
+ (ldap.OPT_X_TLS_CACERTFILE, self._tlsCACertificateFile),
+ (ldap.OPT_X_TLS_CACERTDIR, self._tlsCACertificateDirectory),
+ ):
+ if value is not None:
+ connection.set_option(option, value)
+
+ if self._useTLS:
+ connection.start_tls_s()
+
+ if self.credentials is not None:
+ if IUsernamePassword.providedBy(self.credentials):
+ try:
+ self.ldap.simple_bind_s(
+ self.credentials.username,
+ self.credentials.password,
+ )
+ self.log.info(
+ "Bound to LDAP as {credentials.username}",
+ credentials=self.credentials
+ )
+ except ldap.INVALID_CREDENTIALS as e:
+ self.log.info(
+ "Unable to bind to LDAP as {credentials.username}",
+ credentials=self.credentials
+ )
+ raise LDAPConnectionError(str(e), e)
+
+ else:
+ raise LDAPConnectionError(
+ "Unknown credentials type: {0}"
+ .format(self.credentials)
+ )
+
+ self._connection = connection
+
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+ """
+ LDAP directory record.
+ """
Added: twext/trunk/twext/who/ldap/_util.py
===================================================================
--- twext/trunk/twext/who/ldap/_util.py (rev 0)
+++ twext/trunk/twext/who/ldap/_util.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,165 @@
+# -*- test-case-name: twext.who.ldap.test.test_util -*-
+##
+# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from ..idirectory import QueryNotSupportedError
+from ..expression import (
+ CompoundExpression, Operand,
+ MatchExpression, MatchFlags,
+)
+from ..util import iterFlags
+from ._constants import LDAPMatchType
+
+
+
+def ldapQueryStringFromMatchExpression(expression, attrMap):
+ """
+ Generates an LDAP query string from a match expression.
+
+ @param expression: A match expression.
+ @type expression: L{MatchExpression}
+
+ @param attrMap: A mapping from L{FieldName}s to native LDAP attribute
+ names.
+ @type attrMap: L{dict}
+
+ @return: An LDAP query string.
+ @rtype: C{unicode}
+
+ @raises QueryNotSupportedError: If the expression's match type is unknown,
+ or if the expresion references an unknown field name (meaning a field
+ name not in C{attrMap}).
+ """
+ matchType = LDAPMatchType.fromMatchType(expression.matchType)
+ if matchType is None:
+ raise QueryNotSupportedError(
+ "Unknown match type: {0}".format(matchType)
+ )
+
+ flags = tuple(iterFlags(expression.flags))
+
+ if MatchFlags.NOT in flags:
+ notOp = u"!"
+ else:
+ notOp = u""
+
+ # FIXME: It doesn't look like LDAP queries can be case sensitive.
+ # This would mean that it's up to the callers to filter out the false
+ # positives...
+ #
+ # if MatchFlags.caseInsensitive not in flags:
+ # raise NotImplementedError("Need to handle case sensitive")
+
+ try:
+ attribute = attrMap[expression.fieldName]
+ except KeyError:
+ raise QueryNotSupportedError(
+ "Unknown field name: {0}".format(expression.fieldName)
+ )
+
+ value = unicode(expression.fieldValue) # We want unicode
+ value = value.translate(LDAP_QUOTING_TABLE) # Escape special chars
+
+ return matchType.queryString.format(
+ notOp=notOp, attribute=attribute, value=value
+ )
+
+
+def ldapQueryStringFromCompoundExpression(expression, attrMap):
+ """
+ Generates an LDAP query string from a compound expression.
+
+ @param expression: A compound expression.
+ @type expression: L{MatchExpression}
+
+ @param attrMap: A mapping from L{FieldName}s to native LDAP attribute
+ names.
+ @type attrMap: L{dict}
+
+ @return: An LDAP query string.
+ @rtype: C{unicode}
+
+ @raises QueryNotSupportedError: If any sub-expression cannot be converted
+ to an LDAP query.
+ """
+ queryTokens = []
+
+ if len(expression.expressions) > 1:
+ queryTokens.append(u"(")
+
+ if expression.operand is Operand.AND:
+ queryTokens.append(u"&")
+ else:
+ queryTokens.append(u"|")
+
+ for subExpression in expression.expressions:
+ queryTokens.append(
+ ldapQueryStringFromExpression(subExpression, attrMap)
+ )
+
+ if len(expression.expressions) > 1:
+ queryTokens.append(u")")
+
+ return u"".join(queryTokens)
+
+
+def ldapQueryStringFromExpression(expression, attrMap):
+ """
+ Converts an expression into an LDAP query string.
+
+ @param attrMap: A mapping from L{FieldName}s to native LDAP attribute
+ names.
+ @type attrMap: L{dict}
+
+ @param expression: An expression.
+ @type expression: L{MatchExpression} or L{CompoundExpression}
+
+ @return: A native OpenDirectory query string
+ @rtype: C{unicode}
+
+ @raises QueryNotSupportedError: If the expression cannot be converted to an
+ LDAP query.
+ """
+
+ if isinstance(expression, MatchExpression):
+ return ldapQueryStringFromMatchExpression(expression, attrMap)
+
+ if isinstance(expression, CompoundExpression):
+ return ldapQueryStringFromCompoundExpression(expression, attrMap)
+
+ raise QueryNotSupportedError(
+ "Unknown expression type: {0!r}".format(expression)
+ )
+
+
+LDAP_QUOTING_TABLE = {
+ ord(u"\\"): u"\\5C",
+ ord(u"/"): u"\\2F",
+
+ ord(u"("): u"\\28",
+ ord(u")"): u"\\29",
+ ord(u"*"): u"\\2A",
+
+ ord(u"<"): u"\\3C",
+ ord(u"="): u"\\3D",
+ ord(u">"): u"\\3E",
+ ord(u"~"): u"\\7E",
+
+ ord(u"&"): u"\\26",
+ ord(u"|"): u"\\7C",
+
+ ord(u"\0"): u"\\00",
+}
Added: twext/trunk/twext/who/ldap/test/__init__.py
===================================================================
--- twext/trunk/twext/who/ldap/test/__init__.py (rev 0)
+++ twext/trunk/twext/who/ldap/test/__init__.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
Added: twext/trunk/twext/who/ldap/test/test_service.py
===================================================================
--- twext/trunk/twext/who/ldap/test/test_service.py (rev 0)
+++ twext/trunk/twext/who/ldap/test/test_service.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,33 @@
+##
+# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+LDAP directory service tests.
+"""
+
+from twisted.trial import unittest
+
+# from ...expression import (
+# CompoundExpression, Operand, MatchExpression, MatchType, MatchFlags
+# )
+# from ..ldap import DirectoryService
+
+
+
+class LDAPServiceTestCase(unittest.TestCase):
+ """
+ Tests for L{DirectoryService}.
+ """
Added: twext/trunk/twext/who/ldap/test/test_util.py
===================================================================
--- twext/trunk/twext/who/ldap/test/test_util.py (rev 0)
+++ twext/trunk/twext/who/ldap/test/test_util.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -0,0 +1,219 @@
+##
+# Copyright (c) 2010-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+OpenDirectory service tests.
+"""
+
+from twisted.trial import unittest
+
+from ...expression import (
+ CompoundExpression, Operand, MatchExpression, MatchType, MatchFlags
+)
+from .._service import DirectoryService
+from .._util import (
+ ldapQueryStringFromMatchExpression,
+)
+
+
+
+class LDAPQueryTestCase(unittest.TestCase):
+ """
+ Tests for LDAP query generation.
+ """
+
+ def attrMap(self, service):
+ """
+ Create a mapping from field names to LDAP attribute names.
+ The attribute names returned here are not real LDAP attribute names,
+ but we don't care for these tests, since we're not actually connecting
+ to LDAP.
+ """
+ return dict([(c, c.name) for c in service.fieldName.iterconstants()])
+
+
+ def test_queryStringFromMatchExpression_matchTypes(self):
+ """
+ Match expressions with each match type produces the correct
+ operator=value string.
+ """
+ service = DirectoryService()
+
+ for matchType, expected in (
+ (MatchType.equals, u"=xyzzy"),
+ (MatchType.startsWith, u"=xyzzy*"),
+ (MatchType.endsWith, u"=*xyzzy"),
+ (MatchType.contains, u"=*xyzzy*"),
+ (MatchType.lessThan, u"<xyzzy"),
+ (MatchType.greaterThan, u">xyzzy"),
+ (MatchType.lessThanOrEqualTo, u"<=xyzzy"),
+ (MatchType.greaterThanOrEqualTo, u">=xyzzy"),
+ ):
+ expression = MatchExpression(
+ service.fieldName.shortNames, u"xyzzy",
+ matchType=matchType
+ )
+ queryString = ldapQueryStringFromMatchExpression(
+ expression, self.attrMap(service)
+ )
+ self.assertEquals(
+ queryString,
+ u"({attribute}{expected})".format(
+ attribute="shortNames", expected=expected
+ )
+ )
+
+
+ def test_queryStringFromMatchExpression_match_not(self):
+ """
+ Match expression with the C{NOT} flag adds the C{!} operator.
+ """
+ service = DirectoryService()
+
+ expression = MatchExpression(
+ service.fieldName.shortNames, u"xyzzy",
+ flags=MatchFlags.NOT
+ )
+ queryString = ldapQueryStringFromMatchExpression(
+ expression, self.attrMap(service)
+ )
+ self.assertEquals(
+ queryString,
+ u"(!{attribute}=xyzzy)".format(
+ attribute="shortNames",
+ )
+ )
+
+
+ def test_queryStringFromMatchExpression_match_caseInsensitive(self):
+ """
+ Match expression with the C{caseInsensitive} flag adds the C{??????}
+ operator.
+ """
+ service = DirectoryService()
+
+ expression = MatchExpression(
+ service.fieldName.shortNames, u"xyzzy",
+ flags=MatchFlags.caseInsensitive
+ )
+ queryString = ldapQueryStringFromMatchExpression(
+ expression, self.attrMap(service)
+ )
+ self.assertEquals(
+ queryString,
+ u"???????({attribute}=xyzzy)".format(
+ attribute="shortNames",
+ )
+ )
+
+ test_queryStringFromMatchExpression_match_caseInsensitive.todo = (
+ "unimplemented"
+ )
+
+
+ def test_queryStringFromMatchExpression_match_quoting(self):
+ """
+ Special characters are quoted properly.
+ """
+ service = DirectoryService()
+
+ expression = MatchExpression(
+ service.fieldName.fullNames,
+ u"\\xyzzy: a/b/(c)* ~~ >=< ~~ &| \0!!"
+ )
+ queryString = ldapQueryStringFromMatchExpression(
+ expression, self.attrMap(service)
+ )
+ self.assertEquals(
+ queryString,
+ u"({attribute}={expected})".format(
+ attribute="fullNames",
+ expected=(
+ u"\\5Cxyzzy: a\\2Fb\\2F\\28c\\29\\2A "
+ "\\7E\\7E \\3E\\3D\\3C \\7E\\7E \\26\\7C \\00!!"
+ )
+ )
+ )
+
+
+ # def test_queryStringFromExpression(self):
+ # service = DirectoryService()
+
+ # # CompoundExpressions
+
+ # expression = CompoundExpression(
+ # [
+ # MatchExpression(
+ # service.fieldName.uid, u"a",
+ # matchType=MatchType.contains
+ # ),
+ # MatchExpression(
+ # service.fieldName.guid, u"b",
+ # matchType=MatchType.contains
+ # ),
+ # MatchExpression(
+ # service.fieldName.shortNames, u"c",
+ # matchType=MatchType.contains
+ # ),
+ # MatchExpression(
+ # service.fieldName.emailAddresses, u"d",
+ # matchType=MatchType.startsWith
+ # ),
+ # MatchExpression(
+ # service.fieldName.fullNames, u"e",
+ # matchType=MatchType.equals
+ # ),
+ # ],
+ # Operand.AND
+ # )
+ # queryString = service._queryStringFromExpression(expression)
+ # self.assertEquals(
+ # queryString,
+ # (
+ # u"(&(dsAttrTypeStandard:GeneratedUID=*a*)"
+ # u"(dsAttrTypeStandard:GeneratedUID=*b*)"
+ # u"(dsAttrTypeStandard:RecordName=*c*)"
+ # u"(dsAttrTypeStandard:EMailAddress=d*)"
+ # u"(dsAttrTypeStandard:RealName=e))"
+ # )
+ # )
+
+ # expression = CompoundExpression(
+ # [
+ # MatchExpression(
+ # service.fieldName.shortNames, u"a",
+ # matchType=MatchType.contains
+ # ),
+ # MatchExpression(
+ # service.fieldName.emailAddresses, u"b",
+ # matchType=MatchType.startsWith
+ # ),
+ # MatchExpression(
+ # service.fieldName.fullNames, u"c",
+ # matchType=MatchType.equals
+ # ),
+ # ],
+ # Operand.OR
+ # )
+ # queryString = service._queryStringFromExpression(expression)
+ # self.assertEquals(
+ # queryString,
+ # (
+ # u"(|(dsAttrTypeStandard:RecordName=*a*)"
+ # u"(dsAttrTypeStandard:EMailAddress=b*)"
+ # u"(dsAttrTypeStandard:RealName=c))"
+ # )
+ # )
Deleted: twext/trunk/twext/who/ldap.py
===================================================================
--- twext/trunk/twext/who/ldap.py 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/twext/who/ldap.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -1,198 +0,0 @@
-# -*- test-case-name: twext.who.test.test_ldap -*-
-##
-# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from __future__ import print_function
-
-"""
-LDAP directory service implementation.
-"""
-
-import ldap
-
-# from zope.interface import implementer
-
-from twisted.python.constants import Values, ValueConstant
-# from twisted.internet.defer import succeed, fail
-# from twisted.web.guard import DigestCredentialFactory
-
-from twext.python.log import Logger
-
-from ..idirectory import (
- # DirectoryServiceError, DirectoryAvailabilityError,
- # InvalidDirectoryRecordError, QueryNotSupportedError,
- # FieldName as BaseFieldName,
- RecordType as BaseRecordType,
- # IPlaintextPasswordVerifier, IHTTPDigestVerifier,
-)
-from ..directory import (
- DirectoryService as BaseDirectoryService,
- # DirectoryRecord as BaseDirectoryRecord,
-)
-# from ..expression import (
-# CompoundExpression, Operand,
-# MatchExpression, MatchFlags,
-# )
-from ..util import (
- # iterFlags,
- ConstantsContainer,
-)
-
-
-
-LDAP_QUOTING_TABLE = {
- ord(u"\\"): u"\\5C",
- ord(u"/"): u"\\2F",
-
- ord(u"("): u"\\28",
- ord(u")"): u"\\29",
- ord(u"*"): u"\\2A",
-
- ord(u"<"): u"\\3C",
- ord(u"="): u"\\3D",
- ord(u">"): u"\\3E",
- ord(u"~"): u"\\7E",
-
- ord(u"&"): u"\\26",
- ord(u"|"): u"\\7C",
-
- ord(u"\0"): u"\\00",
-}
-
-
-
-#
-# Exceptions
-#
-
-# class LDAPError(DirectoryServiceError):
-# """
-# LDAP error.
-# """
-
-# def __init__(self, message, odError=None):
-# super(LDAPError, self).__init__(message)
-# self.odError = odError
-
-
-
-# class LDAPConnectionError(DirectoryAvailabilityError):
-# """
-# LDAP connection error.
-# """
-
-# def __init__(self, message, odError=None):
-# super(LDAPConnectionError, self).__init__(message)
-# self.odError = odError
-
-
-
-# class LDAPQueryError(LDAPError):
-# """
-# LDAP query error.
-# """
-
-
-# class LDAPDataError(LDAPError):
-# """
-# LDAP data error.
-# """
-
-
-
-#
-# LDAP Constants
-#
-
-class TLSRequireCertificate(Values):
- never = ValueConstant(ldap.OPT_X_TLS_NEVER)
- allow = ValueConstant(ldap.OPT_X_TLS_ALLOW)
- attempt = ValueConstant(ldap.OPT_X_TLS_TRY)
- demand = ValueConstant(ldap.OPT_X_TLS_DEMAND)
- hard = ValueConstant(ldap.OPT_X_TLS_HARD)
-
-
-#
-# Directory Service
-#
-
-class DirectoryService(BaseDirectoryService):
- """
- LDAP directory service.
- """
- log = Logger()
-
- recordType = ConstantsContainer((
- BaseRecordType.user, BaseRecordType.group,
- ))
-
-
- def __init__(
- self,
- url="ldap://localhost/",
- tlsCACertificateFile=None,
- tlsCACertificateDirectory=None,
- tlsRequireCertificate=None,
- useTLS=False,
- ):
- self._url = url
- self._tlsCACertificateFile = tlsCACertificateFile
- self._tlsCACertificateDirectory = tlsCACertificateDirectory
- self._tlsRequireCertificate = tlsRequireCertificate
- self._useTLS = useTLS,
-
-
- @property
- def realmName(self):
- return u"{self.url}".format(self=self)
-
-
- @property
- def connection(self):
- """
- Get the underlying LDAP connection.
- """
- self._connect()
- return self._connection
-
-
- def _connect(self):
- """
- Connect to the directory server.
-
- @raises: L{LDAPConnectionError} if unable to connect.
- """
- if not hasattr(self, "_connection"):
- connection = ldap.initialize(self._url)
-
- def valueFor(constant):
- if constant is None:
- return None
- else:
- return constant.value
-
- for option, value in (
- (ldap.OPT_X_TLS_CACERTFILE, self._tlsCACertificateFile),
- (ldap.OPT_X_TLS_CACERTDIR, self._tlsCACertificateDirectory),
- (ldap.OPT_X_TLS, valueFor(self._tlsRequireCertificate)),
- ):
- if value is not None:
- connection.set_option(option, value)
-
- if self._useTLS:
- connection.start_tls_s()
-
- self._connection = connection
Modified: twext/trunk/twext/who/opendirectory/__init__.py
===================================================================
--- twext/trunk/twext/who/opendirectory/__init__.py 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/twext/who/opendirectory/__init__.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -24,7 +24,6 @@
"OpenDirectoryQueryError",
"OpenDirectoryDataError",
"DirectoryService",
- "DirectoryRecord",
"NoQOPDigestCredentialFactory",
]
Modified: twext/trunk/twext/who/opendirectory/_constants.py
===================================================================
--- twext/trunk/twext/who/opendirectory/_constants.py 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/twext/who/opendirectory/_constants.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -1,4 +1,3 @@
-# -*- test-case-name: twext.who.opendirectory.test.test_service -*-
##
# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
#
@@ -388,8 +387,8 @@
class ODMatchType(Values):
- all = ValueConstant(0x0001)
- all.queryString = u"({notOp}{attribute}=*)"
+ any = ValueConstant(0x0001)
+ any.queryString = u"({notOp}{attribute}=*)"
equals = ValueConstant(0x2001)
equals.matchType = MatchType.equals
Modified: twext/trunk/twext/who/opendirectory/_service.py
===================================================================
--- twext/trunk/twext/who/opendirectory/_service.py 2014-01-09 22:18:11 UTC (rev 12278)
+++ twext/trunk/twext/who/opendirectory/_service.py 2014-01-10 03:39:29 UTC (rev 12279)
@@ -43,6 +43,7 @@
CompoundExpression, Operand,
MatchExpression, MatchFlags,
)
+from ..ldap._util import LDAP_QUOTING_TABLE
from ..util import iterFlags, ConstantsContainer
from ._odframework import ODSession, ODNode, ODQuery
@@ -52,27 +53,6 @@
-LDAP_QUOTING_TABLE = {
- ord(u"\\"): u"\\5C",
- ord(u"/"): u"\\2F",
-
- ord(u"("): u"\\28",
- ord(u")"): u"\\29",
- ord(u"*"): u"\\2A",
-
- ord(u"<"): u"\\3C",
- ord(u"="): u"\\3D",
- ord(u">"): u"\\3E",
- ord(u"~"): u"\\7E",
-
- ord(u"&"): u"\\26",
- ord(u"|"): u"\\7C",
-
- ord(u"\0"): u"\\00",
-}
-
-
-
#
# Exceptions
#
@@ -298,7 +278,7 @@
"""
Generates an LDAP query string from a compound expression.
- @param expression: A match expression.
+ @param expression: A compound expression.
@type expression: L{MatchExpression}
@return: An LDAP query string.
@@ -425,7 +405,7 @@
recordTypes = ODRecordType.fromRecordType(
expression.fieldValue
).value
- matchType = ODMatchType.all.value
+ matchType = ODMatchType.any.value
queryAttribute = None
queryValue = None
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/ea093fab/attachment.html>
More information about the calendarserver-changes
mailing list