Esempio n. 1
0
 def __init__(self, props=None):
     connector = LDAPConnector(props=props)
     communicator = LDAPCommunicator(connector)
     communicator.baseDN = 'dc=my-domain,dc=com'
     communicator.bind()
     res = communicator.search('(objectclass=*)', ldap.SCOPE_BASE, 
                               'cn=subschema', attrlist=['*','+'])                                       
     if len(res) != 1:
         raise ValueError, 'subschema not found'        
     self.subschema = ldap.schema.SubSchema(ldap.cidict.cidict(res[0][1]))
Esempio n. 2
0
 def subschema(self):
     if hasattr(self, '_subschema'):
         return self._subschema
     connector = LDAPConnector(props=self._props)
     communicator = LDAPCommunicator(connector)
     communicator.bind()
     res = communicator.search('(objectclass=*)', ldap.SCOPE_BASE,
                               'cn=subschema', attrlist=['*', '+'])
     if len(res) != 1:
         raise ValueError('subschema not found')
     self._subschema = ldap.schema.SubSchema(ldap.cidict.cidict(res[0][1]))
     return self._subschema
Esempio n. 3
0
 def subschema(self):
     if hasattr(self, '_subschema'):
         return self._subschema
     connector = LDAPConnector(props=self._props)
     communicator = LDAPCommunicator(connector)
     communicator.bind()
     res = communicator.search('(objectclass=*)',
                               ldap.SCOPE_BASE,
                               'cn=subschema',
                               attrlist=['*', '+'])
     if len(res) != 1:
         raise ValueError('subschema not found')
     self._subschema = ldap.schema.SubSchema(ldap.cidict.cidict(res[0][1]))
     return self._subschema
Esempio n. 4
0
def getLdapCommunicator(base):
    """
    :param base: soit "users" soit "groups"
    :type base: str
    :returns: un communicateur de node.ext.ldap qui permet de réaliser
        des requêtes au serveur LDAP
    """
    props = getLdapProps()
    conn = LDAPConnector(props=props)
    comm = LDAPCommunicator(conn)
    if base in ['u', 'U', 'users']:
        comm.baseDN = getSettingValue('users_base')
    else:
        comm.baseDN = getSettingValue('groups_base')
    comm.bind()
    return comm
Esempio n. 5
0
    def test_base(self):
        # NullCachManager registration
        provideAdapter(NullCacheManager)

        # Test main script, could be used by command line with
        # 'python base.py server port'::
        old_argv = sys.argv
        sys.argv = ['base.py', '127.0.0.1', '12345']
        self.assertEqual(main(), 'success')

        sys.argv[-1] = '12346'
        self.assertEqual(main().args[0], {
            'info': 'Transport endpoint is not connected',
            'errno': 107,
            'desc': "Can't contact LDAP server"
        })

        sys.argv = []
        self.assertEqual(main(), 'usage: python base.py [server] [port]')

        sys.argv = old_argv

        # Test node.ext.ldap base objects. Test LDAP connectivity
        self.assertEqual(testLDAPConnectivity('127.0.0.1', 12345), 'success')
        self.assertEqual(testLDAPConnectivity('127.0.0.1', 12346).args[0], {
            'info': 'Transport endpoint is not connected',
            'errno': 107,
            'desc': u"Can't contact LDAP server"}
        )

        # LDAP credentials
        host = "127.0.0.1"
        port = 12345
        binddn = "cn=Manager,dc=my-domain,dc=com"
        bindpw = "secret"

        props = LDAPProps(
            server=host,
            port=port,
            user=binddn,
            password=bindpw
        )

        # Create connector.
        connector = LDAPConnector(props=props)

        # Create communicator.
        communicator = LDAPCommunicator(connector)

        # Bind to directory.
        communicator.bind()

        # Search fails if baseDN is not set and not given to search function
        self.assertEqual(communicator.baseDN, '')

        err = self.expect_error(
            ValueError,
            communicator.search,
            '(objectClass=*)',
            SUBTREE
        )
        self.assertEqual(str(err), 'baseDN unset.')

        # Set base dn and check if previously imported entries are present.
        communicator.baseDN = 'dc=my-domain,dc=com'
        res = communicator.search('(objectClass=*)', SUBTREE)
        self.assertEqual(len(res), 7)

        # Test search pagination
        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=''
        )
        self.assertEqual(len(res), 4)

        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=cookie
        )
        self.assertEqual(len(res), 3)

        self.assertEqual(cookie, b'')

        # Pagination search fails if cookie but no page size given
        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=''
        )
        err = self.expect_error(
            ValueError,
            communicator.search,
            '(objectClass=*)',
            SUBTREE,
            cookie=cookie
        )
        self.assertEqual(str(err), 'cookie passed without page_size')

        # Test inserting entries.
        entry = {
            'cn': b'foo',
            'sn': b'bar',
            'objectclass': (b'person', b'top'),
        }
        dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com'
        communicator.add(dn, entry)

        # Now there's one more entry in the directory.
        res = communicator.search('(objectClass=*)', SUBTREE)
        self.assertEqual(len(res), 8)

        # Query added entry directly.
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']}
        )])

        # Modify this entry and check the result.
        communicator.modify(res[0][0], [(MOD_REPLACE, 'sn', b'baz')])
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'baz']}
        )])

        # Finally delete this entry and check the result.
        communicator.delete(res[0][0])
        self.assertEqual(communicator.search('(cn=foo)', SUBTREE), [])

        # Unbind from server.
        communicator.unbind()

        # Connector using cache.
        connector = LDAPConnector(props)
        communicator = LDAPCommunicator(connector)
        communicator.bind()

        # Add entry
        entry = {
            'cn': b'foo',
            'sn': b'bar',
            'objectclass': (b'person', b'top'),
        }
        dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com'
        communicator.add(dn, entry)
        communicator.baseDN = 'dc=my-domain,dc=com'

        # Search cached entry. Does not get cached here since no real cache
        # provider is registered. Thus the nullcacheProviderFactory is used.
        # But cache API is used anyways::
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']}
        )])

        # Delete entry
        communicator.delete(res[0][0])
        res = communicator.search('(cn=foo)', SUBTREE, force_reload=True)
        self.assertEqual(res, [])

        communicator.unbind()
Esempio n. 6
0
    def test_base(self):
        # NullCachManager registration
        provideAdapter(NullCacheManager)

        # Test main script, could be used by command line with
        # 'python base.py server port'::
        old_argv = sys.argv
        sys.argv = ['base.py', '127.0.0.1', '12345']
        self.assertEqual(main(), 'success')

        sys.argv[-1] = '12346'
        self.assertEqual(main().args[0], {
            'info': 'Transport endpoint is not connected',
            'errno': 107,
            'desc': "Can't contact LDAP server"
        })

        sys.argv = []
        self.assertEqual(main(), 'usage: python base.py [server] [port]')

        sys.argv = old_argv

        # Test node.ext.ldap base objects. Test LDAP connectivity
        self.assertEqual(testLDAPConnectivity('127.0.0.1', 12345), 'success')
        self.assertEqual(testLDAPConnectivity('127.0.0.1', 12346).args[0], {
            'info': 'Transport endpoint is not connected',
            'errno': 107,
            'desc': u"Can't contact LDAP server"}
        )

        # LDAP credentials
        host = "127.0.0.1"
        port = 12345
        binddn = "cn=Manager,dc=my-domain,dc=com"
        bindpw = "secret"

        props = LDAPProps(
            server=host,
            port=port,
            user=binddn,
            password=bindpw
        )

        # Create connector.
        connector = LDAPConnector(props=props)

        # Create communicator.
        communicator = LDAPCommunicator(connector)

        # Bind to directory.
        communicator.bind()

        # Search fails if baseDN is not set and not given to search function
        self.assertEqual(communicator.baseDN, '')

        err = self.expect_error(
            ValueError,
            communicator.search,
            '(objectClass=*)',
            SUBTREE
        )
        self.assertEqual(str(err), 'baseDN unset.')

        # Set base dn and check if previously imported entries are present.
        communicator.baseDN = 'dc=my-domain,dc=com'
        res = communicator.search('(objectClass=*)', SUBTREE)
        self.assertEqual(len(res), 7)

        # Test search pagination
        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=''
        )
        self.assertEqual(len(res), 4)

        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=cookie
        )
        self.assertEqual(len(res), 3)

        self.assertEqual(cookie, b'')

        # Pagination search fails if cookie but no page size given
        res, cookie = communicator.search(
            '(objectClass=*)',
            SUBTREE,
            page_size=4,
            cookie=''
        )
        err = self.expect_error(
            ValueError,
            communicator.search,
            '(objectClass=*)',
            SUBTREE,
            cookie=cookie
        )
        self.assertEqual(str(err), 'cookie passed without page_size')

        # Test inserting entries.
        entry = {
            'cn': b'foo',
            'sn': b'bar',
            'objectclass': (b'person', b'top'),
        }
        dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com'
        communicator.add(dn, entry)

        # Now there's one more entry in the directory.
        res = communicator.search('(objectClass=*)', SUBTREE)
        self.assertEqual(len(res), 8)

        # Query added entry directly.
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']}
        )])

        # Modify this entry and check the result.
        communicator.modify(res[0][0], [(MOD_REPLACE, 'sn', b'baz')])
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'baz']}
        )])

        # Finally delete this entry and check the result.
        communicator.delete(res[0][0])
        self.assertEqual(communicator.search('(cn=foo)', SUBTREE), [])

        # Unbind from server.
        communicator.unbind()

        # Connector using cache.
        connector = LDAPConnector(props)
        communicator = LDAPCommunicator(connector)
        communicator.bind()

        # Add entry
        entry = {
            'cn': b'foo',
            'sn': b'bar',
            'objectclass': (b'person', b'top'),
        }
        dn = 'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com'
        communicator.add(dn, entry)
        communicator.baseDN = 'dc=my-domain,dc=com'

        # Search cached entry. Does not get cached here since no real cache
        # provider is registered. Thus the nullcacheProviderFactory is used.
        # But cache API is used anyways::
        res = communicator.search('(cn=foo)', SUBTREE)
        self.assertEqual(res, [(
            'cn=foo,ou=customer1,ou=customers,dc=my-domain,dc=com',
            {'objectClass': [b'person', b'top'], 'cn': [b'foo'], 'sn': [b'bar']}
        )])

        # Delete entry
        communicator.delete(res[0][0])
        res = communicator.search('(cn=foo)', SUBTREE, force_reload=True)
        self.assertEqual(res, [])

        communicator.unbind()
Esempio n. 7
0
class LDAPSession(object):
    """LDAP Session binds always.

    all strings must be utf8 encoded!
    """
    def __init__(self, props):
        self._props = props
        connector = LDAPConnector(props=props)
        self._communicator = LDAPCommunicator(connector)

    def checkServerProperties(self):
        """Test if connection can be established.
        """
        res = testLDAPConnectivity(props=self._props)
        if res == 'success':
            return (True, 'OK')
        else:
            return (False, res)

    @property
    def baseDN(self):
        baseDN = self._communicator.baseDN
        return baseDN

    @baseDN.setter
    def baseDN(self, baseDN):
        """baseDN must be utf8-encoded.
        """
        self._communicator.baseDN = baseDN

    def ensure_connection(self):
        """If LDAP directory is down, bind again and retry given function.

        XXX: * Improve retry logic
             * Extend LDAPSession object to handle Fallback server(s)
        """
        if self._communicator._con is None:
            self._communicator.bind()

    def search(self,
               queryFilter='(objectClass=*)',
               scope=BASE,
               baseDN=None,
               force_reload=False,
               attrlist=None,
               attrsonly=0,
               page_size=None,
               cookie=None):
        if not queryFilter:
            # It makes no sense to really pass these to LDAP, therefore, we
            # interpret them as "don't filter" which in LDAP terms is
            # '(objectClass=*)'
            queryFilter = '(objectClass=*)'
        self.ensure_connection()
        res = self._communicator.search(queryFilter, scope, baseDN,
                                        force_reload, attrlist, attrsonly,
                                        page_size, cookie)
        if page_size:
            res, cookie = res
        # ActiveDirectory returns entries with dn None, which can be ignored
        res = filter(lambda x: x[0] is not None, res)
        if page_size:
            return res, cookie
        return res

    def add(self, dn, data):
        self.ensure_connection()
        self._communicator.add(dn, data)

    def authenticate(self, dn, pw):
        """Verify credentials, but don't rebind the session to that user
        """
        # Let's bypass connector/communicator until they are sorted out
        if self._props.ignore_cert:
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
        con = ldap.initialize(self._props.uri)
        # Turning referrals off since they cause problems with MS Active
        # Directory More info: https://www.python-ldap.org/faq.html#usage
        con.set_option(ldap.OPT_REFERRALS, 0)
        try:
            con.simple_bind_s(dn, pw)
        except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM):
            # The UNWILLING_TO_PERFORM event might be thrown, if you query a
            # local user named ``admin``, but the LDAP server is configured to
            # deny such queries. Instead of raising an exception, just ignore
            # this.
            return False
        else:
            return True

    def modify(self, dn, data, replace=False):
        """Modify an existing entry in the directory.

        :param dn: Modification DN
        :param data: Either list of 3 tuples (look at
            ``node.ext.ldap.base.LDAPCommunicator.modify`` for details), or a
            dictionary representing the entry or parts of the entry.
            XXX: dicts not yet
        :param replace: If set to True, replace entry at DN entirely with data.
        """
        self.ensure_connection()
        result = self._communicator.modify(dn, data)
        return result

    def delete(self, dn):
        self._communicator.delete(dn)

    def passwd(self, userdn, oldpw, newpw):
        self.ensure_connection()
        result = self._communicator.passwd(userdn, oldpw, newpw)
        return result

    def unbind(self):
        self._communicator.unbind()
Esempio n. 8
0
 def __init__(self, props):
     self._props = props
     connector = LDAPConnector(props=props)
     self._communicator = LDAPCommunicator(connector)
Esempio n. 9
0
class LDAPSession(object):
    """LDAP Session binds always.
    """

    def __init__(self, props):
        self._props = props
        connector = LDAPConnector(props=props)
        self._communicator = LDAPCommunicator(connector)

    def checkServerProperties(self):
        """Test if connection can be established.
        """
        res = testLDAPConnectivity(props=self._props)
        if res == 'success':
            return (True, 'OK')
        else:
            return (False, res)

    @property
    def baseDN(self):
        baseDN = self._communicator.baseDN
        return baseDN

    @baseDN.setter
    def baseDN(self, baseDN):
        self._communicator.baseDN = baseDN

    def ensure_connection(self):
        """If LDAP directory is down, bind again and retry given function.
        """
        if self._communicator._con is None:
            self._communicator.bind()

    def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None,
               force_reload=False, attrlist=None, attrsonly=0,
               page_size=None, cookie=None):
        if not queryFilter:
            # It makes no sense to really pass these to LDAP, therefore, we
            # interpret them as "don't filter" which in LDAP terms is
            # '(objectClass=*)'
            queryFilter = '(objectClass=*)'
        self.ensure_connection()
        res = self._communicator.search(
            queryFilter,
            scope,
            baseDN,
            force_reload,
            attrlist,
            attrsonly,
            page_size,
            cookie
        )
        if page_size:
            res, cookie = res
        # ActiveDirectory returns entries with dn None, which can be ignored
        res = [x for x in res if x[0] is not None]
        if page_size:
            return res, cookie
        return res

    def add(self, dn, data):
        self.ensure_connection()
        self._communicator.add(dn, data)

    def authenticate(self, dn, pw):
        """Verify credentials, but don't rebind the session to that user
        """
        # Let's bypass connector/communicator until they are sorted out
        if self._props.ignore_cert:  # pragma: no cover
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
        con = ldap.initialize(
            self._props.uri,
            bytes_mode=False,
            bytes_strictness='silent'
        )
        # Turning referrals off since they cause problems with MS Active
        # Directory More info: https://www.python-ldap.org/faq.html#usage
        con.set_option(ldap.OPT_REFERRALS, 0)
        try:
            con.simple_bind_s(dn, pw)
        except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM):
            # The UNWILLING_TO_PERFORM event might be thrown, if you query a
            # local user named ``admin``, but the LDAP server is configured to
            # deny such queries. Instead of raising an exception, just ignore
            # this.
            return False
        else:
            return True

    def modify(self, dn, data, replace=False):
        """Modify an existing entry in the directory.

        :param dn: Modification DN
        :param data: Either list of 3 tuples (look at
            ``node.ext.ldap.base.LDAPCommunicator.modify`` for details), or a
            dictionary representing the entry or parts of the entry.
            XXX: dicts not yet
        :param replace: If set to True, replace entry at DN entirely with data.
        """
        self.ensure_connection()
        result = self._communicator.modify(dn, data)
        return result

    def delete(self, dn):
        self._communicator.delete(dn)

    def passwd(self, userdn, oldpw, newpw):
        self.ensure_connection()
        result = self._communicator.passwd(userdn, oldpw, newpw)
        return result

    def unbind(self):
        self._communicator.unbind()
Esempio n. 10
0
 def __init__(self, props):
     self._props = props
     connector = LDAPConnector(props=props)
     self._communicator = LDAPCommunicator(connector)
Esempio n. 11
0
class LDAPSession(object):

    def __init__(self, props):
        self._props = props
        connector = LDAPConnector(props=props)
        self._communicator = LDAPCommunicator(connector)

    def checkServerProperties(self):
        """Test if connection can be established.
        """
        res = testLDAPConnectivity(props=self._props)
        if res == 'success':
            return (True, 'OK')
        else:
            return (False, res)

    def _get_baseDN(self):
        baseDN = self._communicator.baseDN
        baseDN = decode(baseDN)
        return baseDN

    def _set_baseDN(self, baseDN):
        if isinstance(baseDN, str):
            # make sure its utf8
            baseDN = decode(baseDN)
        baseDN = encode(baseDN)
        self._communicator.baseDN = baseDN

    baseDN = property(_get_baseDN, _set_baseDN)

    def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None,
               force_reload=False, attrlist=None, attrsonly=0, page_size=None, cookie=None):
        
        #if self._props.escape_queries and baseDN is not None:
        #    baseDN = escape(baseDN)
        
        if queryFilter in ('', u'', None):
            # It makes no sense to really pass these to LDAP, therefore, we
            # interpret them as "don't filter" which in LDAP terms is
            # '(objectClass=*)'
            queryFilter='(objectClass=*)'
        
        func = self._communicator.search

        # bug in node when using string: https://github.com/bluedynamics/node/issues/5
        if isinstance(cookie, str):
            cookie = unicode(cookie)

        res = self._perform(func, queryFilter, scope, baseDN,
                            force_reload, attrlist, attrsonly, page_size, cookie)
        
        # ActiveDirectory returns entries with dn None, which can be ignored
        if page_size:
            res, cookie = res

        res = filter(lambda x: x[0] is not None, res)
        if page_size:
            return res, cookie
        else:
            return res

    def add(self, dn, data):
        func = self._communicator.add
        return self._perform(func, dn, data)

    def authenticate(self, dn, pw):
        """Verify credentials, but don't rebind the session to that user
        """
        # Let's bypass connector/communicator until they are sorted out
        con = ldap.initialize(self._props.uri)
        try:
            con.simple_bind_s(dn, pw)
        except ldap.INVALID_CREDENTIALS:
            return False
        else:
            return True

    def modify(self, dn, data, replace=False):
        """Modify an existing entry in the directory.

        dn
            Modification DN
        
        #data
        #    either list of 3 tuples (look at
        #    node.ext.ldap.base.LDAPCommunicator.modify for details), or
        #    a dictionary representing the entry or parts of the entry.
        #    XXX: dicts not yet
        
        replace
            if set to True, replace entry at DN entirely with data.
        """
        func = self._communicator.modify
        return self._perform(func, dn, data)

    def delete(self, dn):
        func = self._communicator.delete
        return self._perform(func, dn)

    def passwd(self, userdn, oldpw, newpw):
        func = self._communicator.passwd
        self._perform(func, userdn, oldpw, newpw)

    def unbind(self):
        self._communicator.unbind()

    def _perform(self, function, *args, **kwargs):
        """Try to perform the given function with the given argument.

        If LDAP directory is down, bind again and retry given function.

        XXX: * Improve retry logic in LDAPSession
             * Extend LDAPSession object to handle Fallback server(s)
        """
        args = encode(args)
        kwargs = encode(kwargs)
        if self._communicator._con is None:
            self._communicator.bind()
        
        # XXX: it seems except block is never reached, call of
        #      self._communicator.bind() above already raises error if
        #      communication fails. And why server down?
        #try:
        #    return decode(function(*args, **kwargs))
        #except ldap.SERVER_DOWN:
        #    self._communicator.bind()
        #    return decode(function(*args, **kwargs))
        
        return decode(function(*args, **kwargs))
Esempio n. 12
0
class LDAPSession(object):
    """LDAP Session binds always.

    all strings must be utf8 encoded!
    """

    def __init__(self, props):
        self._props = props
        connector = LDAPConnector(props=props)
        self._communicator = LDAPCommunicator(connector)

    def checkServerProperties(self):
        """Test if connection can be established.
        """
        res = testLDAPConnectivity(props=self._props)
        if res == 'success':
            return (True, 'OK')
        else:
            return (False, res)

    def _get_baseDN(self):
        baseDN = self._communicator.baseDN
        return baseDN

    def _set_baseDN(self, baseDN):
        """baseDN must be utf8-encoded.
        """
        self._communicator.baseDN = baseDN

    baseDN = property(_get_baseDN, _set_baseDN)

    def ensure_connection(self):
        """If LDAP directory is down, bind again and retry given function.

        XXX: * Improve retry logic
             * Extend LDAPSession object to handle Fallback server(s)
        """
        if self._communicator._con is None:
            self._communicator.bind()

    def search(self, queryFilter='(objectClass=*)', scope=BASE, baseDN=None,
               force_reload=False, attrlist=None, attrsonly=0,
               page_size=None, cookie=None):
        if not queryFilter:
            # It makes no sense to really pass these to LDAP, therefore, we
            # interpret them as "don't filter" which in LDAP terms is
            # '(objectClass=*)'
            queryFilter = '(objectClass=*)'
        self.ensure_connection()
        res = self._communicator.search(queryFilter, scope, baseDN,
                                        force_reload, attrlist, attrsonly,
                                        page_size, cookie)
        if page_size:
            res, cookie = res
        # ActiveDirectory returns entries with dn None, which can be ignored
        res = filter(lambda x: x[0] is not None, res)
        if page_size:
            return res, cookie
        return res

    def add(self, dn, data):
        self.ensure_connection()
        self._communicator.add(dn, data)

    def authenticate(self, dn, pw):
        """Verify credentials, but don't rebind the session to that user
        """
        # Let's bypass connector/communicator until they are sorted out
        if self._props.ignore_cert:
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
        con = ldap.initialize(self._props.uri)
        try:
            con.simple_bind_s(dn, pw)
        except (ldap.INVALID_CREDENTIALS, ldap.UNWILLING_TO_PERFORM):
            # The UNWILLING_TO_PERFORM event might be thrown, if you query a
            # local user named ``admin``, but the LDAP server is configured to
            # deny such queries. Instead of raising an exception, just ignore
            # this.
            return False
        else:
            return True

    def modify(self, dn, data, replace=False):
        """Modify an existing entry in the directory.

        dn
            Modification DN

        #data
        #    either list of 3 tuples (look at
        #    node.ext.ldap.base.LDAPCommunicator.modify for details), or
        #    a dictionary representing the entry or parts of the entry.
        #    XXX: dicts not yet

        replace
            if set to True, replace entry at DN entirely with data.
        """
        self.ensure_connection()
        result = self._communicator.modify(dn, data)
        return result

    def delete(self, dn):
        self._communicator.delete(dn)

    def passwd(self, userdn, oldpw, newpw):
        self.ensure_connection()
        result = self._communicator.passwd(userdn, oldpw, newpw)
        return result

    def unbind(self):
        self._communicator.unbind()