[Checkins] SVN: zc.winauth/trunk/src/zc/winauth/ Initial import. A pluggable authentication plugin for interacting with Windows authentication.

Gary Poster gary at zope.com
Tue Aug 15 17:27:30 EDT 2006


Log message for revision 69546:
  Initial import.  A pluggable authentication plugin for interacting with Windows authentication.
  

Changed:
  A   zc.winauth/trunk/src/zc/winauth/README.txt
  A   zc.winauth/trunk/src/zc/winauth/__init__.py
  A   zc.winauth/trunk/src/zc/winauth/configure.zcml
  A   zc.winauth/trunk/src/zc/winauth/integration.txt
  A   zc.winauth/trunk/src/zc/winauth/interfaces.py
  A   zc.winauth/trunk/src/zc/winauth/tests.py
  A   zc.winauth/trunk/src/zc/winauth/winauth.py

-=-
Added: zc.winauth/trunk/src/zc/winauth/README.txt
===================================================================
--- zc.winauth/trunk/src/zc/winauth/README.txt	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/README.txt	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,169 @@
+======================
+Windows Authentication
+======================
+
+This package provides for authenticating windows users with their domain
+user names and passwords.
+
+---------------------------
+WindowsAuthenticationPlugin
+---------------------------
+
+The authentication is done via a WindowsAuthenticationPlugin:
+
+    >>> from zc.winauth.winauth import WindowsAuthenticationPlugin
+    >>> wap = WindowsAuthenticationPlugin()
+  
+When a user needs to be authenticated there credentials are passed to the
+plugin:
+
+    >>> credentials = {'login': 'jdoe', 'password': 'pass'}
+
+The result of authentication is information about the principal:
+
+    >>> info = wap.authenticateCredentials(credentials)
+    >>> info is not None
+    True
+
+The IDs are constructed using Windows' SIDs:
+
+    >>> info.id
+    'zc.winauth.S-...'
+
+They also have a title:
+
+    >>> info.title
+    u'John Doe'
+
+If an unknown user name or incorrect password are given, None is returned:
+
+    >>> badCredentials = {'login': 'jdoe', 'password': 'wrong'}
+    >>> wap.authenticateCredentials(badCredentials) is None
+    True
+
+If the credentials are None, then None is returned:
+
+    >>> print wap.authenticateCredentials(None)
+    None
+
+
+------
+Prefix
+------
+
+The plugin uses a prefix for all principal IDs:
+
+    >>> wap.prefix
+    'zc.winauth'
+
+If an ID is passed to principalInfo that doesn't have the right prefix, None is
+returned:
+
+    >>> print wap.principalInfo('different.prefix')
+    None
+
+-------------
+Server Errors
+-------------
+
+If an error occurrs while checking a user's password, the credentials are
+denied:
+
+    >>> bool(wap.checkPassword('error', 'pass'))
+    False
+
+Something similar happens if an error occurrs while retrieving principal info:
+
+    >>> print wap.principalInfo('zc.winauth.error')
+    None
+
+If the error is that the domain controller we're using is no longer available,
+the result will be as if the user doesn't exist (dc_is_dead is just a knob for
+testing).
+
+    >>> win32net.dc_is_dead = True
+    >>> print wap.authenticateCredentials(credentials)
+    None
+    >>> print wap.principalInfo('zc.winauth.jsmith')
+    None
+    >>> win32net.dc_is_dead = False
+
+-------------
+Missing Title
+-------------
+
+If a user has an empty "full_name", their user name will be used instead:
+
+    >>> info = wap.principalInfo('zc.winauth.S-1-5-2')
+    >>> info.title
+    'jsmith'
+
+-------------------------
+Repeating bad credentials
+-------------------------
+
+In the face of bad credentials, authentication will only be attempted once
+during a timeout period.  This is to resolve problems when a policy of locking
+out accounts after a number of failed login attempts is in effect.
+
+    >>> credentials = {'login': 'jdoe', 'password': 'pass'}
+    >>> wap.authenticateCredentials(credentials) is not None
+    True
+
+If the credentials suddenly start failing...
+
+    >>> win32security.all_logins_are_bad = True
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+
+...they will continue to fail.
+
+    >>> win32security.all_logins_are_bad = False
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+
+After a timeout, the credentials will work again.
+
+    >>> import zc.winauth.winauth
+    >>> original_timeout = zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT
+    >>> zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT = 0
+    >>> wap.authenticateCredentials(credentials) is not None
+    True
+    >>> zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT = original_timeout
+
+It is common for credentials == None at some point *between* authentication of
+the real credentials.  So, if again the credentials initially work...
+
+    >>> credentials = {'login': 'jdoe', 'password': 'pass'}
+    >>> wap.authenticateCredentials(credentials) is not None
+    True
+
+...and then suddenly start failing...
+
+    >>> win32security.all_logins_are_bad = True
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+
+...they will continue to fail, even if None credentials are tried between.
+
+    >>> win32security.all_logins_are_bad = False
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+    >>> wap.authenticateCredentials(None) is not None
+    False
+    >>> wap.authenticateCredentials(credentials) is not None
+    False
+
+After a timeout, the credentials will work again.
+
+    >>> import zc.winauth.winauth
+    >>> original_timeout = zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT
+    >>> zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT = 0
+    >>> wap.authenticateCredentials(credentials) is not None
+    True
+
+We need to set the timeout back.
+
+    >>> zc.winauth.winauth.BAD_CREDENTIALS_TIMEOUT = original_timeout


Property changes on: zc.winauth/trunk/src/zc/winauth/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.winauth/trunk/src/zc/winauth/__init__.py
===================================================================
--- zc.winauth/trunk/src/zc/winauth/__init__.py	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/__init__.py	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1 @@
+#

Added: zc.winauth/trunk/src/zc/winauth/configure.zcml
===================================================================
--- zc.winauth/trunk/src/zc/winauth/configure.zcml	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/configure.zcml	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,20 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    i18n_domain="zc.winauth">
+
+  <localUtility class=".winauth.WindowsAuthenticationPlugin">
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.authentication.interfaces.IAuthenticatorPlugin"
+        />
+  </localUtility>
+
+  <browser:addMenuItem
+      title="Windows Authentication Plugin"
+      description="A Windows PAU Authentication Plugin"
+      class=".winauth.WindowsAuthenticationPlugin"
+      permission="zope.ManageContent"
+      />
+
+</configure>

Added: zc.winauth/trunk/src/zc/winauth/integration.txt
===================================================================
--- zc.winauth/trunk/src/zc/winauth/integration.txt	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/integration.txt	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,44 @@
+======================
+Windows Authentication
+======================
+
+This package provides for authenticating windows users with their domain
+user names and passwords.
+
+WindowsAuthenticationPlugin
+===========================
+
+The authentication is done via a WindowsAuthenticationPlugin.
+
+    >>> from zc.winauth.winauth import WindowsAuthenticationPlugin
+    >>> wap = WindowsAuthenticationPlugin()
+
+When a user needs to be authenticated there credentials are passed to the
+plugin.
+
+    >>> credentials = {'login': 'testy', 'password': 'test'}
+
+The result of authentication is the principal's name with a prefix, and a
+little information about the principal.
+
+    >>> info = wap.authenticateCredentials(credentials)
+    >>> info is not None
+    True
+
+Winauth uses the Windows SID as part of the ID.
+
+    >>> info.id
+    u'...S-...-...-...'
+    >>> info.title
+    u'Testy Testerson'
+
+If an unknown user name or incorrect password are given, None is returned.
+
+    >>> badCredentials = {'login': 'testy', 'password': 'wrong'}
+    >>> wap.authenticateCredentials(badCredentials) is None
+    True
+
+If a bad ID is used to get principal info, no info is returned:
+
+    >>> print wap.principalInfo('zc.winauth.bad')
+    None


Property changes on: zc.winauth/trunk/src/zc/winauth/integration.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zc.winauth/trunk/src/zc/winauth/interfaces.py
===================================================================
--- zc.winauth/trunk/src/zc/winauth/interfaces.py	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/interfaces.py	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Additional interfaces for zc.winauth.
+
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+
+
+class IUserInfo(zope.interface.Interface):
+
+    """Additional user information provided by the zc.winauth plugin."""
+
+    name = zope.schema.TextLine(
+        title=u"Short name for the login account",
+        description=u"This is usually the login name for the user.",
+        )

Added: zc.winauth/trunk/src/zc/winauth/tests.py
===================================================================
--- zc.winauth/trunk/src/zc/winauth/tests.py	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/tests.py	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,179 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+import sys, unittest, warnings, os
+import doctest
+from zope.testing.doctest import DocFileSuite
+import zc.winauth.winauth
+
+
+class FakeHandle:
+    def __init__(self, sid):
+        self.sid = sid
+
+    def Close(self):
+        pass
+
+
+class FakeError:
+    def __init__(self, *args):
+        self.args = args
+
+    def __getitem__(self, index):
+        return self.args[index]
+
+
+class FakeWin32netcon:
+    FILTER_NORMAL_ACCOUNT = 0
+
+
+class FakeWin32security:
+    LOGON32_LOGON_NETWORK = 1
+    LOGON32_PROVIDER_DEFAULT = 2
+    TokenUser = 3
+    all_logins_are_bad = False
+
+    def LogonUser(self, userName, domain, password, logonType, logonProvider):
+        # if we are currently dis-allowing all logins...
+        if self.all_logins_are_bad:
+            raise pywintypes.error(1326)
+
+        if userName == 'jdoe' and password == 'pass':
+            return FakeHandle(sid='S-1-5-1')
+        elif userName == 'error':
+            raise pywintypes.error(999999999)
+        else:
+            raise pywintypes.error(1326)
+
+    def GetTokenInformation(self, handle, token_type):
+        return [handle.sid]
+
+    def ConvertSidToStringSid(self, sid):
+        return sid
+
+    def GetBinarySid(self, sid):
+        if sid.startswith('S-'):
+            return sid
+        else:
+            raise ValueError
+
+    def LookupAccountSid(self, domain, sid):
+        assert domain is None
+        if sid == 'S-1-5-1':
+            return ['jdoe']
+        if sid == 'S-1-5-2':
+            return ['jsmith']
+        raise RuntimeError('uknown SID "%s"' % sid)
+
+    def LookupAccountName(self, domain, name):
+        assert domain is None
+        if name == 'jdoe':
+            return ['S-1-5-1']
+        if name == 'jsmith':
+            return ['S-1-5-2']
+        if name == 'extra':
+            return ['S-1-5-3']
+        raise RuntimeError('uknown name "%s"' % name)
+
+
+class FakePywintypes:
+    error = FakeError
+
+
+class FakeWin32net:
+    dc_is_dead = False
+
+    def NetUserGetInfo(self, server, username, *args):
+        if username == 'jdoe':
+            return {'name': username, 'full_name': u'John Doe'}
+        elif username == 'jsmith':
+            return {'name': username, 'full_name': u''}
+        else:
+            raise pywintypes.error(1326)
+
+    def NetGetDCName(self, *args):
+        if self.dc_is_dead:
+            raise FakeError(999999999)
+        return 'DomainControllerName'
+
+    def NetServerGetInfo(self, *args):
+        if self.dc_is_dead:
+            raise FakeError(999999999)
+
+    def NetUserEnum(self, server, level, filter, handle, prefLen=None):
+        if handle == 0:
+            data = [self.NetUserGetInfo(server, username)
+                    for username in ['jdoe', 'jsmith']]
+            return data, len(data)+1, 1
+        else:
+            data = [{'name': 'extra', 'full_name': 'Extra Guy'}]
+            return data, len(data)+1, 0
+
+
+def setUp(test):
+    # set up some fake modules
+    global pywintypes
+    global win32net
+    pywintypes = zc.winauth.winauth.pywintypes = FakePywintypes()
+    win32net = zc.winauth.winauth.win32net = FakeWin32net()
+    win32security = zc.winauth.winauth.win32security = FakeWin32security()
+    zc.winauth.winauth.win32netcon = FakeWin32netcon()
+    test.globs['win32net'] = win32net
+    test.globs['win32security'] = win32security
+
+def tearDown(test):
+    global pywintypes
+    global win32net
+    # remove the fake modules
+    if sys.platform.startswith('win'):
+        import pywintypes, win32security, win32net, win32netcon
+        zc.winauth.winauth.pywintypes = pywintypes
+        zc.winauth.winauth.win32security = win32security
+        zc.winauth.winauth.win32net = win32net
+        zc.winauth.winauth.win32netcon = win32netcon
+    else:
+        del pywintypes
+        del win32net
+        del zc.winauth.winauth.pywintypes
+        del zc.winauth.winauth.win32security
+        del zc.winauth.winauth.win32net
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown,
+                  optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+        ))
+
+    if sys.platform.startswith('win') and 'FIPS_DEVEL_MODE' not in os.environ:
+        wap = zc.winauth.winauth.WindowsAuthenticationPlugin()
+
+        # if it appears that the test user has been configured, do the
+        # integration tests
+        import pywintypes, win32net
+        try:
+            win32net.NetGetDCName(None, None)
+        except pywintypes.error, e:
+            warnings.warn('this machine is apparently not part of a domain;'
+                          ' Windows authentication tests are being skipped.')
+        else:
+            if wap.searchUsers('Testy Testerson', 0, 10):
+                suite.addTest(DocFileSuite('integration.txt',
+                    optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS))
+            else:
+                warnings.warn('The test user is not set up; Windows'
+                              ' authentication tests are being skipped.')
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: zc.winauth/trunk/src/zc/winauth/winauth.py
===================================================================
--- zc.winauth/trunk/src/zc/winauth/winauth.py	2006-08-15 21:25:55 UTC (rev 69545)
+++ zc.winauth/trunk/src/zc/winauth/winauth.py	2006-08-15 21:27:29 UTC (rev 69546)
@@ -0,0 +1,248 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Windows Authentication Plugin."""
+import sys, logging, time, re
+
+import persistent
+
+from zope import interface
+from zope.location.interfaces import ILocation
+import zope.app.authentication
+import zope.app.authentication.interfaces
+import zope.app.error.interfaces
+
+import zc.security.interfaces
+import zc.winauth.interfaces
+
+BAD_CREDENTIALS_TIMEOUT = 30
+
+if sys.platform.startswith('win'):
+    import pywintypes, win32security, win32net, win32netcon
+    import pythoncom, win32api
+    from win32com.client import GetObject
+    from win32com.client.gencache import EnsureDispatch
+
+
+class PrincipalInfo(object):
+    interface.implements(
+        zope.app.authentication.interfaces.IPrincipalInfo,
+        zc.winauth.interfaces.IUserInfo)
+
+    def __init__(self, id, title, description='', name=''):
+        self.id = id
+        self.title = title
+        self.description = description
+        self.name = name
+
+
+class WindowsAuthenticationPlugin(persistent.Persistent):
+    interface.implements(
+        zope.app.authentication.interfaces.IAuthenticatorPlugin,
+        zc.security.interfaces.ISimpleUserSearch,
+        ILocation) # TODO remove this
+
+    __name__ = __parent__ = None
+    _server = None
+    prefix = 'zc.winauth'
+    last_bad_credentials = None
+    last_bad_time = 0
+
+    def __init__(self):
+        assert win32security
+
+    def authenticateCredentials(self, credentials):
+        if credentials is None:
+            return
+
+        if (time.time() - self.last_bad_time < BAD_CREDENTIALS_TIMEOUT and
+            credentials == self.last_bad_credentials):
+            return
+
+        # this if is to prevent a write-on-read
+        if self.last_bad_credentials is not None:
+            self.last_bad_credentials = None
+            self.last_bad_time = 0
+
+        username = credentials['login']
+        password = credentials['password']
+
+        logging.info('Begining authentication for "%s".' % username)
+        info = None
+        uid = self.checkPassword(username, password)
+        if uid is None:
+            self.last_bad_credentials = credentials
+            self.last_bad_time = time.time()
+        else:
+            info = self.principalInfo(self.prefix + '.' + uid)
+
+        logging.info('Done with authentication for "%s".' % username)
+        return info
+
+    # For this method to work, the user running the code requires the
+    # privilege to "Act as part of the Operating System" (SE_TCB_NAME)
+    # on Windows 2000 (but not XP or 2003).  To add it go to Control Panel/
+    # Administrative Tools/Local Security Policy.  Expand Local Policies
+    # and choose User Rights Assignment.  Double-click on "Act as part of
+    # the operating system" and add the desired user.  The user must log
+    # out and back in for the new privilege to take effect.
+    def checkPassword(self, username, password):
+        logging.info('Authenticating credentials for "%s".' % username)
+        try:
+            handle=win32security.LogonUser(username, None, password,
+                          # We use LOGON32_LONGON_NETWORK because it's faster.
+                          win32security.LOGON32_LOGON_NETWORK,
+                          win32security.LOGON32_PROVIDER_DEFAULT)
+
+            sid = win32security.GetTokenInformation(handle,
+                                                    win32security.TokenUser)[0]
+            # we got back a binary SID, we want a string representation
+            sid = win32security.ConvertSidToStringSid(sid)
+            # We're not going to use the handle, just seeing if we can get it.
+            handle.Close()
+        except pywintypes.error, e:
+            # Because of the sheer number of windows-specific errors that can
+            # occur here, we have to assume any of them mean that the
+            # credentials were not valid.
+
+            # log the exception
+            logging.warning('An exception occurred while attempting to '
+                            'authenticate the user "%s".' % username,
+                            exc_info=sys.exc_info())
+            # something bad happened, so the user can't be authenticated
+            result = None
+        else:
+            result = sid
+
+
+        logging.info('Done authenticating credentials for "%s".' % username)
+        return result
+
+    def principalInfo(self, id):
+        if not id.startswith(self.prefix+'.'):
+            return
+
+        string_sid = id[len(self.prefix)+1:]
+
+        try:
+            sid = win32security.GetBinarySid(string_sid)
+        except ValueError:
+            return None
+
+        username = win32security.LookupAccountSid(None, sid)[0]
+
+        while True:
+            try:
+                # try to use the current server to get user info
+                info = win32net.NetUserGetInfo(self.server, username, 10)
+            except pywintypes.error, e:
+                # Because of the sheer number of windows-specific errors
+                # that can occur here, we have to assume any of them mean
+                # that the user's info couldn't be retrieved.
+
+                # log the exception
+                logging.warning('An exception occurred while attempting '
+                                'to get information about the user "%s".'
+                                % username, exc_info=sys.exc_info())
+                # something bad happened, so the user's info can't be retrieved
+                return
+            break
+
+        title = info['full_name']
+        if not title:
+            title = username
+        description = info.get('comment') or ''
+        name = info.get('name') or ''
+
+        return PrincipalInfo(id, title, description, name)
+
+    @property
+    def server(self):
+        while True:
+            if self._server is None:
+                try:
+                    self._server = win32net.NetGetDCName(None, None)
+                except pywintypes.error:
+                    logging.warn('An exception occurred while attempting '
+                                 'to contact a domain controller.',
+                                 exc_info=sys.exc_info())
+                    raise
+
+            try:
+                # make sure the server is still alive, if it is, this won't
+                # raise an exception
+                win32net.NetServerGetInfo(self._server, 100)
+            except pywintypes.error:
+                # the server appears to be down, forget it's name and try again
+                self._server = None
+                logging.warn('The domain controller went away, will try to '
+                             'find another one.', exc_info=sys.exc_info())
+                continue
+            break
+        return self._server
+
+    def searchUsers(self, filter, start, size):
+        def getSid(username):
+            sid = win32security.LookupAccountName(None, username)[0]
+            sid = win32security.ConvertSidToStringSid(sid)
+            return sid
+
+        def getConnection():
+            connection = EnsureDispatch('ADODB.Connection')
+            connection.Provider = 'ADsDSOObject'
+            connection.Open('Active Directory Provider')
+            return connection
+
+        def query(query_string):
+            command = EnsureDispatch('ADODB.Command')
+            command.ActiveConnection = getConnection()
+            command.CommandText = query_string
+
+            recordset, result = command.Execute()
+            while not recordset.EOF:
+                yield GetObject(str(recordset.Fields.Item(0)))
+                recordset.MoveNext()
+
+        def buildWhere(filter):
+            chunks = filter.strip().split(' ')
+            chunks = ["displayName='*%s*'" % chunk for chunk in chunks]
+            return ' and '.join(chunks)
+
+        def filterIsSafe(filter):
+            return re.match(r'^(\w|\s)*$', filter)
+
+        if not filterIsSafe(filter):
+            return []
+
+        pythoncom.CoInitialize()
+        results = []
+        domain_name = win32api.GetDomainName()
+        root = GetObject('LDAP://%s/rootDSE' % domain_name)
+        dnc = root.Get('defaultNamingContext')
+        ADsPath = GetObject('LDAP://%s/%s' % (domain_name, dnc)).ADsPath
+
+        where = "objectCategory = 'Person'"
+
+        if filter.strip():
+            where = where + 'and ' + buildWhere(filter)
+
+        for user in query("select * from '%s' where %s" % (ADsPath, where)):
+            if user.sAMAccountName:
+                results.append(self.prefix + '.' + getSid(user.sAMAccountName))
+
+            # see if we've found enough to stop
+            if len(results) >= start + size:
+                break
+
+        return results[start:start+size]



More information about the Checkins mailing list