Giter VIP home page Giter VIP logo

ldap3's Introduction

LDAP3

Latest Version License TRAVIS-CI build status for master branch

ldap3 is a strictly RFC 4510 conforming LDAP V3 pure Python client library. The same codebase runs in Python 2, Python 3, PyPy and PyPy3.

A more pythonic LDAP

LDAP operations look clumsy and hard-to-use because they reflect the old-age idea that time-consuming operations should be performed client-side to not hog the server with heavy elaborations. To alleviate this ldap3 includes a fully functional Abstraction Layer that lets you interact with the LDAP server in a modern and pythonic way. With the Abstraction Layer you don't need to directly issue any LDAP operation at all.

Thread safe strategies

In multithreaded programs you must use one of SAFE_SYNC (synchronous connection strategy), SAFE_RESTARTABLE (restartable syncronous connection strategy) or ASYNC (asynchronous connection strategy).

Each LDAP operation with SAFE_SYNC or SAFE_RESTARTABLE strategies returns a tuple of four elements: status, result, response and request.

  • status: states if the operation was successful
  • result: the LDAP result of the operation
  • response: the response of a LDAP Search Operation
  • request: the original request of the operation

The SafeSync strategy can be used with the Abstract Layer, but the Abstract Layer currently is NOT thread safe. For example, to use SAFE_SYNC:

from ldap3 import Server, Connection, SAFE_SYNC
server = Server('my_server')
conn = Connection(server, 'my_user', 'my_password', client_strategy=SAFE_SYNC, auto_bind=True)
status, result, response, _ = conn.search('o=test', '(objectclass=*)')  # usually you don't need the original request (4th element of the returned tuple)

With ASYNC you must request the response with the get_response() method.

Home Page

The home page of the ldap3 project is https://github.com/cannatag/ldap3

Documentation

Documentation is available at http://ldap3.readthedocs.io

License

The ldap3 project is open source software released under the LGPL v3 license. Copyright 2013 - 2020 Giovanni Cannata

PEP8 Compliance

ldap3 is PEP8 compliant, except for line length.

Download

Package download is available at https://pypi.python.org/pypi/ldap3.

Install

Install with pip install ldap3

Git repository

You can download the latest source at https://github.com/cannatag/ldap3

Continuous integration

Continuous integration for testing is at https://travis-ci.org/cannatag/ldap3

Support & Development

You can submit support tickets on https://github.com/cannatag/ldap3/issues/new You can submit pull request on the dev branch at https://github.com/cannatag/ldap3/tree/dev

Thanks to

  • Ilya Etingof, the author of the pyasn1 package for his excellent work and support.
  • Mark Lutz for his Learning Python and Programming Python excellent books series and John Goerzen and Brandon Rhodes for their book Foundations of Python Network Programming. These books are wonderful tools for learning Python and this project owes a lot to them.
  • JetBrains for donating to this project the Open Source license of PyCharm Professional.
  • GitHub for providing the free source repository space and the tools I use to develop this project.
  • The FreeIPA team for letting me use their demo LDAP server in the ldap3 tutorial.

Contact me

For information and suggestions you can contact me at [email protected]. You can also open a support ticket on https://github.com/cannatag/ldap3/issues/new

Donate

If you want to keep this project up and running you can send me an Amazon gift card. I will use it to improve my skills in Information and Communication technologies.

Changelog

Updated changelog at https://ldap3.readthedocs.io/changelog.html

ldap3's People

Contributors

alexdutton avatar azornberg-ps avatar busuwe avatar cannatag avatar ccsalway avatar cfelder avatar clodoaldopinto avatar cravaterouge avatar dirkjanm avatar elex-puru avatar felixilgatto avatar gregn610 avatar jerrykan avatar jo-con-el avatar kahlertl avatar krizex avatar m-aciek avatar martinrohn avatar mprahl avatar nazarii avatar noritada avatar optik-aper avatar phi1010 avatar roxanagherle avatar s0undt3ch avatar styopa avatar thepiratewhosmellsofsunflowers avatar turingtux avatar yykamei avatar zorn96 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ldap3's Issues

ldap3

In Python 3.x on OpenBSD 5.7, socket.AI_V4MAPPED is not defined. I am afraid AI_V4MAPPED is not supported by the OS.
As a result, core/server.address_info()'s reference to the constant causes an error.
I would like you to remove the flag unless it it is absolutely needed,

Support for GSSAPI/SASL

First, thanks for a really well done library! It's already been extraordinarily helpful for us.

Our local setup uses the GSSAPI SASL mechanism to authenticate to LDAP via Kerberos. From reading the code, I don't think this is currently supported by ldap3, though it looks like some other SASL mechanisms are supported already.

I'm not sure how practical it is, but it'd be really cool if this was supported in some future release.

I notice there's a python-gssapi module, which looks like it might be helpful, but it's using a C extension to interface with GSSAPI, which I'm guessing might be an issue.

I'm going to try to get something working with python-gssapi as a starting point, but any insights you have would be greatly appreciated!

Argument conversion with submitting cross domain search, following referrals

Short version: When searching from a top level domain against a sub-domain, an LDAPInvalidScopeError is thrown. When printed to the console, the arguments are of different types than those expected by the value checks.

With an authenticated bind to:
dc=mydomain,dc=tld

I submit the following to the search function:
search_base 'cn=users,dc=subdomain,dc=mydomain,dc=tld'
search_filter '(objectclass=*)'
search_scope 'SUBTREE'
size_limit 500

I put pass the arguments for search_operation() in ldap3/operation/search.py to print(), prior to any value checks, and get the following output:
search_base 'cn=users,dc=subdomain,dc=mydomain,dc=tld'
search_filter '(objectclass=*)'
search_scope 2
dereference_aliases 3
attributes ['1.1']
size_limit 0
time_limit 0
types_only False
schema None

Sorry for all the bug reports. Ready to test changes, please advise.

`ssl.SSLError` exception not raised on certificate error

Somewhere between commits 568b69a...6e918c3 the ssl.SSLError exception is no longer raised when a certificate issue is detected. Using the following example:

import inspect
import ssl

import ldap3

# username, password, host, port defined here
server = ldap3.Server(host, port=port, use_ssl=True,
                      tls=ldap3.Tls(validate=ssl.CERT_REQUIRED))
conn = ldap3.Connection(server, user=username, password=password,
                        raise_exceptions=True)
try:
    conn.open()
except Exception as e:
    from pprint import pprint
    pprint(inspect.getmro(type(e)))

the following is output in 568b69a

(<class 'ldap3.core.exceptions.LDAPSocketOpenError'>,
 <class 'ldap3.core.exceptions.LDAPSocketOpenError'>,
 <class 'ldap3.core.exceptions.LDAPCommunicationError'>,
 <class 'ldap3.core.exceptions.LDAPExceptionError'>,
 <class 'ldap3.core.exceptions.LDAPException'>,
 <class 'ssl.SSLError'>,
 <class 'socket.error'>,
 <type 'exceptions.IOError'>,
 <type 'exceptions.EnvironmentError'>,
 <type 'exceptions.StandardError'>,
 <type 'exceptions.Exception'>,
 <type 'exceptions.BaseException'>,
 <type 'object'>)

but in 6e918c3 it is as follows:

(<class 'ldap3.core.exceptions.LDAPSocketOpenError'>,
 <class 'ldap3.core.exceptions.LDAPCommunicationError'>,
 <class 'ldap3.core.exceptions.LDAPExceptionError'>,
 <class 'ldap3.core.exceptions.LDAPException'>,
 <type 'exceptions.Exception'>,
 <type 'exceptions.BaseException'>,
 <type 'object'>)

There should be some way to differentiate between a connection error due to a port not actually being open and an error due to certificate verification problems.

Operator ~= bug

In operation/search.py, line 109,
left_part, _, right_part = match.split('=')
should be
left_part, _, right_part = match.partition('
=')

(like other operators)

modify_password error if no hash method specified

File "/lib/python3.4/site-packages/ldap3/extend/init.py", line 61, in modify_password
return ModifyPassword(self._connection, user, old_password, new_password, hashed).send()
File "/lib/python3.4/site-packages/ldap3/extend/standard/modifyPassword.py", line 52, in init
self.request_value['newPasswd'] = hashed(hash_algorithm, new_password, salt)
File "/lib/python3.4/site-packages/ldap3/utils/hashed.py", line 81, in hashed
digest = hashlib.new(algorithm, value).digest()
File "/lib/python3.4/hashlib.py", line 122, in __hash_new
return _hashlib.new(name, data)
TypeError: name must be a string

Add support for SASL NTLM

It would be nice if ldap3 would support SASL NTLM, to be able to bind to active directorY, that has no domain controller certificate enrolled.

Cannot use controls with empty control values.

Hello,

I'm trying to implement an LDAP login screen interfacing with an OpenLDAP server. Normal negotiations are executing fine, however the OpenLDAP server I'm negotiating with also implements Password Policy Overlay. This overlay requires its Control Request to contain two fields. The Control Value field should not be sent, otherwise OpenLDAP server returns a ProtocolError (even if the field is empty).

Unfortunately there's no way to not to send the value field with the control requests. Adding the ability to send controls with no value fields is necessary to implement Password Policy Overlay. It's not possible to implement Password Policy Overlay.

connection fails to fall back to ipv4 if ipv6 fails

Hello,

For some reason I thought this was already fixed, but it seems like I was mistaken. I can't find that bug right now.

I get Connection Refused when connecting to localhost, if localhost resolves as ::1 and 127.0.0.1, and when the ldap server is only listening on 127.0.0.1

If the IPv6 connection fails, it should fall back to the IPv4 address automatically.

Regards

Where is search_sub_tree() ?

Documentation mentions a .search_sub_tree() method on Reader in abstraction.rst:146 but the code is nowhere to be found. Is the function still available ?

I would really like to take advantage of this in an ongoing project.

Generators for attribute values not accepted by Connection.modify()

I'm generating data for my database using generators but at the point it's being passed back to the database it's being interpreted incorrectly. I'm more or less doing:

new_values = (i for i in range(3))
changes = {'testAttribute' : new_values}
connection.modify('ou=test,dc=example,dc=com', changes)

which ends up writing something like (retrieved using ldapsearch on the command line):

dn: ou=test,dc=example,dc=com
testAttribute:: PGdlbmVyYXRvciBvYmplY3QgPGdlbmV4cHI+IGF0IDB4N2Y3YTQ0ZjQwYTY4Pg==

which is the base64 encoded version of:

dn: ou=test,dc=example,dc=com
testAttribute: <generator object <genexpr> at 0x7f7a44f40a68>

but I would expect:

dn: ou=test,dc=example,dc=com
testAttribute: 0
testAttribute: 1
testAttribute: 2

So it's writing the __str__ version of the object rather than iterating its values. This seems to come down to the explicit type testing happening at https://github.com/cannatag/ldap3/blob/master/ldap3/operation/modify.py#L64 by testing against ldap3.SEQUENCE_TYPES. Perhaps it would be better to use collections.abc.Iterable with a special case to filter out string types?

I expect this applies to Connection.add() as well.

Cannot access 0.9.8.2 from pypi anymore

Hi, I've got a package that has a pinned requirement to 0.9.8.2 which doesn't seem to be available anymore on pypi. I'm just wondering if that was removed from pypi for a specific reason, or if it would be possible to keep old versions available there.

thanks!

Install Error ldap3-0.9.9.1 - Missing DLL

I'm trying to install ldap3-0.9.9.1 version, but I'm getting the error below. How can I fix this? I'm using Windows 7 - 64bit with python 3.4.3 version for 64 bit.

ldpa3 error

py3 byte/string inconsitancy in modify_password

    new_password = c.extend.standard.modify_password('uid=boris,ou=people,dc=foo,dc=bar,dc=com', 'OLDPASS', 'NEWPASS', 'SALTED_SHA256')
  File "/usr/lib64/python3.4/site-packages/ldap3/extend/__init__.py", line 61, in modify_password
    return ModifyPassword(self._connection, user, old_password, new_password, hashed).send()
  File "/usr/lib64/python3.4/site-packages/ldap3/extend/standard/modifyPassword.py", line 52, in __init__
    self.request_value['newPasswd'] = hashed(hash_algorithm, new_password, salt)
  File "/usr/lib64/python3.4/site-packages/ldap3/utils/hashed.py", line 74, in hashed
    digest = hashed(salted_table[algorithm][1], value + salt, raw=True) + salt
TypeError: Can't convert 'bytes' object to str implicitly

Samba does not honor delete_old_dn=True

When attempting to rename an object with connection.modify_dn(), an error is raised with the message ERROR, 00002035: Unwilling to perform. Old RDN must be deleted on Samba domain controllers. With full debug logging on, it appears that the correct information is being transmitted to the server. This operation has been tested against Microsoft Server 2008 R2 and OpenLDAP with success.

I've had similar issues in the past with the python-ldap library but currently I cannot find the code to illustrate resolution. This issue may be specifically Samba related but thought it should be noted here.

performance issue with _getkey in cidict.py

Hi,

I made some performance comparisons between AD and OpenLDAP using ldap3, and I found that AD was very very slow.

After some investigations with a profiling tools, I found that a lot of time was spent in _getkey from cidict.py. A simple BIND takes 5s, cidict.py was called 5491 time (the same test with OpenLdap reports 0.09s and 662 calls).

After updating the code by adding a _insensitive_key_map dict to save the correspondance between key.lower() and key, and removing the loop, the performance was 0.67s for the BIND in AD and 0.07s for OpenLDAP.

Best regards,

Pierre Mellier

conn.modify documentation issue

It looks like there is a documentation error on this page:
http://ldap3.readthedocs.org/en/latest/connections.html

Specifically, the changes structure for the modify operation is listed as:

{โ€˜attribute1โ€™: [(operation, [val1, val2, ...])], โ€˜attribute2โ€™: [(operation, [val1, val2, ...])]}

This does not work, giving ldap3.core.exceptions.LDAPChangesError: malformed change. Looking at the test code, it should be:

{โ€˜attribute1โ€™: (operation, [val1, val2, ...]), โ€˜attribute2โ€™: (operation, [val1, val2, ...])}

Thanks!
(There operation Tuple should not be enclosed in a list.)

easy_install is broken

Hi,

trying to install this package with easy_install fails.

$ easy_install-3.4 ldap3
Searching for ldap3
Reading https://pypi.python.org/simple/ldap3/
Best match: ldap3 0.9.7.1
Downloading https://pypi.python.org/packages/source/l/ldap3/ldap3-0.9.7.1.tar.gz#md5=da7e3dc4d6c41e406cfe9de24f91ded8
Processing ldap3-0.9.7.1.tar.gz
Writing /tmp/easy_install-2ai16tq3/ldap3-0.9.7.1/setup.cfg
Running ldap3-0.9.7.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-2ai16tq3/ldap3-0.9.7.1/egg-dist-tmp-yai_rxoa
error: [Errno 2] No such file or directory: '_version.py'

pip install does work, probably because it unpacks and runs setup in the same directory. You could define your _version.py attributes in setup.cfg

search query fails with not operator

Hello.

Given the ldap search filter:

(&(objectClass=person)(objectClass=top)(objectClass=inetOrgPerson (objectClass=organizationalPerson)(!(uid=tuz)))

I am getting the error:

[...]
  File "/home/brian/tree/karaage/python-tldap/tldap/backend/base.py", line 162, in first_results
    base, filterstr, scope, attributes=attrlist, paged_size=limit)
  File "/usr/lib/python2.7/dist-packages/ldap3/core/connection.py", line 568, in search
    request = search_operation(search_base, search_filter, search_scope, dereference_aliases, attributes, size_limit, time_limit, types_only, self.server.schema if self.server else None)
  File "/usr/lib/python2.7/dist-packages/ldap3/operation/search.py", line 365, in search_operation
    request['filter'] = build_filter(search_filter, schema)  # parse the searchFilter string and compile it starting from the root node
  File "/usr/lib/python2.7/dist-packages/ldap3/operation/search.py", line 312, in build_filter
    return compile_filter(parse_filter(search_filter, schema).elements[0])
  File "/usr/lib/python2.7/dist-packages/ldap3/operation/search.py", line 243, in compile_filter
    boolean_filter[pos] = compile_filter(element)
  File "/usr/lib/python2.7/dist-packages/ldap3/operation/search.py", line 256, in compile_filter
    compiled_filter['notFilter'] = boolean_filter
  File "/usr/lib/python2.7/dist-packages/pyasn1/type/univ.py", line 824, in __setitem__
    self.setComponentByName(idx, value)
  File "/usr/lib/python2.7/dist-packages/pyasn1/type/univ.py", line 859, in setComponentByName
    self._componentType.getPositionByName(name),value,verifyConstraints
  File "/usr/lib/python2.7/dist-packages/pyasn1/type/univ.py", line 1093, in setComponentByPosition
    self._verifyComponent(idx, value)
  File "/usr/lib/python2.7/dist-packages/pyasn1/type/univ.py", line 848, in _verifyComponent
    raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, t))
PyAsn1Error: Component value is tag-incompatible: Not(componentType=NamedTypes(NamedType('innerNotFilter', Filter(componentType=NamedTypes(NamedType('and', And(componentType=None)), NamedType('or', Or(componentType=None)), NamedType('notFilter', Not(componentType=NamedTypes(), tagSet=TagSet((), ))), NamedType('equalityMatch', EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('substringFilter', SubstringFilter(componentType=NamedTypes(NamedType('type', AttributeDescription()), NamedType('substrings', Substrings())))), NamedType('greaterOrEqual', GreaterOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('lessOrEqual', LessOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('present', Present()), NamedType('approxMatch', ApproxMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('extensibleMatch', ExtensibleMatch(componentType=NamedTypes(OptionalNamedType('matchingRule', MatchingRule()), OptionalNamedType('type', Type()), NamedType('matchValue', MatchValue()), DefaultedNamedType('dnAttributes', DnAttributes('False')))))))))).setComponents(Filter(componentType=NamedTypes(NamedType('and', And(componentType=None)), NamedType('or', Or(componentType=None)), NamedType('notFilter', Not(componentType=NamedTypes(), tagSet=TagSet((), ))), NamedType('equalityMatch', EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('substringFilter', SubstringFilter(componentType=NamedTypes(NamedType('type', AttributeDescription()), NamedType('substrings', Substrings())))), NamedType('greaterOrEqual', GreaterOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('lessOrEqual', LessOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('present', Present()), NamedType('approxMatch', ApproxMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('extensibleMatch', ExtensibleMatch(componentType=NamedTypes(OptionalNamedType('matchingRule', MatchingRule()), OptionalNamedType('type', Type()), NamedType('matchValue', MatchValue()), DefaultedNamedType('dnAttributes', DnAttributes('False'))))))).setComponents(None, None, None, EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue()))).setComponents(AttributeDescription('uid'), AssertionValue('tuz')))) vs Not(componentType=NamedTypes(), tagSet=TagSet((), ))

----------------------------------------------------------------------

This was working, I suspect an update in another package may have broken it.

This is with python-ldap3 version 0.9.8.4

pyasn1 is the obvious suspect. Debian/jessie has version 0.1.7, which works; Debian/sid has version 0.1.8 which fails. Will try to prove if the change in version of pyasn1 causes the problem or not.

modify'ing binary attributes

Hello,
It's entirely possible I'm doing it wrong, but I've been unsuccessful updating the thumbnailPhoto attribute.

The following code works using ldap with python 2.x
f = open('path/to/img.jpg', 'rb')
img = f.read()
ok = ldap_obj.modify(dn, [(ldap.MOD_REPLACE, 'thumbnailPhoto', img)])

I realize that f.read() in python3 returns bytes so i've tried casting to str or calling .decode with both utf-8 and cp437 as the format, in all cases, this fails:

foto = str(img)

foto = img.decode('cp437')

ok = ldap_obj.modify(dn, {'thumbnailPhoto': (ldap3.MODIFY_REPLACE, [foto]) })

using version 0.9.7.2 on python 3.4.2

Error on non-writable stream on read_only file connection

As far as I understand, I could use this package to parse an LDIF file like this:

from ldap3 import Connection, LDIF
c = Connection(None, client_strategy=LDIF, read_only=True)
c.stream = open('foo.ldif', 'w')
with c:
    ...

If the stream object is not writable (i.e. not stream.writable()) this won't work. I guess this is the desired effect when talking to a real server. But it is at least confusing when parsing a file.

I can see multiple solutions to this:

  • document the current behavior
  • clearly state in the docs that this library is not meant for parsing LDIF files
  • allow a non-writable stream if server is None and read_only is true
  • provide an altogether separate way to parse files

Maybe a solution is aleady in place and I just did not find it. In that case I would be thankful for a hint.

Example for SASL DIGEST-MD5

Is there an example for using the SASL credentials with creating the connection?
This does not work for me:

c = Connection(s,
           auto_bind = False,
           client_strategy = SYNC,
           sasl_mechanism="DIGEST-MD5",
           sasl_credentials=('cn=mydn,dc=organization,'mypassword'),
           authentication=SASL,
           check_names=True)
c.open()
r = c.bind()
c.unbind()

r will always be False.

Error retrieving entries from Connection object.

On Samba and Windows, when attempting to retrieve entries with entries(), a KeyError is thrown for the attributes key.

>>> connection.entries()
Traceback (most recent call last):
File "", line 1, in
File "/venv/lib/python3.4/site-packages/ldap3/core/connection.py", line 1174, in entries
self._entries = self._get_entries(self.response)
File "/venv/lib/python3.4/site-packages/ldap3/core/connection.py", line 1184, in _get_entries
resp_attr_set = set(response['attributes'].keys())
KeyError: 'attributes'

It appears that the issue is in the response object. connection.py iterates over connection.response and uses the key attributes to access the dictionary. However, on Samba and Windows, connection.response also contains elements of type: searchResRef with referral uri's for other naming contexts. For example:

[{'attributes': {'givenName': 'john'},
'dn': 'CN=jbaker,OU=Engineering,OU=departments,DC=mydomain,DC=com',
'raw_attributes': {'givenName': [b'john']},
'type': 'searchResEntry'},
{'type': 'searchResRef',
'uri': ['ldap://mydomain.com/CN=Configuration,DC=mydomain,DC=com']},
{'type': 'searchResRef',
'uri': ['ldap://mydomain.com/DC=DomainDnsZones,DC=mydomain,DC=com']},
{'type': 'searchResRef',
'uri': ['ldap://mydomain.com/DC=ForestDnsZones,DC=mydomain,DC=com']}]

Am I missing something? I'm really enjoying this module. Thanks for the great work you've done.

Get sent & received messages with LDAP server

Hi,

Is there a way to intercept messages that are sent and received from the LDAP server?
It would be useful for troubleshooting.
Right now I was thinking about extending a client strategy (i.e. extend the SYNC strategy) methods to get the messages but it doesn't seems to be the best way of doing that. Any idea?

Thanks!
--Phil

Attempts to connect to an LDAP server fail with "LDAPSocketOpenError: invalid server address" on FreeBSD

Any attempt to connect to an LDAP server fails with "LDAPSocketOpenError: invalid server address" on FreeBSD. This is because getaddrinfo(3) on FreeBSD does not accept AI_V4MAPPED flag as a hint, and fails with error code 3 ("Invalid value for ai_flags") if this flag is set. The following patch fixed (or worked around) the problem for me: https://gist.github.com/andriy-s/ac502e1d27ba4f19912d

FAIL: test_valid_simple_alphanumeric_password_with_non_ascii_characters (test.testSaslPrep.Test)

I saw these tests failing on python 2.x in your travis CI.
It's failing at your assertion comparing a bytes literal to the variable 'validated'.

ldap3.protocol.sasl.sasl.validate_simple_password will give any non-bytes password a pass through sasl_prep, then if it's not running in python 3.x, it will byte encode the prepped result.

The problem is, in python 2.x sasl_prep also returns a unicode string instead of 2.x's standard byte-based str. So it needs encoded to bytes either way, and it's not being done running on 2.x.

I forked and fixed it, I'll submit the pull request when my own CI tests finish running.

Cannot get c.search to work

Here is my code:

from ldap3 import Server, Connection, ALL, BASE, STRATEGY_SYNC, ANONYMOUS
s = Server('dmc.solarcity.local',port=389,get_info=ALL)
with Connection(server=s,auto_bind=True,client_strategy=STRATEGY_SYNC,authentication=ANONYMOUS) as c:
print(c.result)
c.search(search_base='OU=SolarCity Users,DC=SolarCity,DC=local',search_filter='(objectclass=user)',search_scope=BASE)
print(c.result)

that outputs the following:

{'referrals': None, 'description': 'success', 'dn': '', 'type': 'bindResponse',
'saslCreds': None, 'result': 0, 'message': ''}
{'referrals': None, 'description': 'operationsError', 'message': '000004DC: Ldap
Err: DSID-0C0906E8, comment: In order to perform this operation a successful bin
d must be completed on the connection., data 0, v1db1\x00', 'dn': '', 'type': 's
earchResDone', 'result': 1}

Why is the bind successful and then in the very next line saying that it is not successful? Thanks.

EDIT: I figured it out, thanks, it was an issue on my end.

How to set the filter_string argument for the SEARCH operation?

Hello ldap3 team,

I'm trying to use your library to authenticate with an LDAP server. I'm not sure I'm performing the search operation correctly and would really appreciate it if you guys could help with the expected syntax (RFC4515 compliant) of the search_filter argument. I've opened a question on stackoverflow and would really appreciate it if you guys could help.

Please find the link below:

Thank you.

Regards,
Khalid F. Al Khalili

ConnectionUsage.update_transmitted_message() crash on bind request

Hi,

I just noticed that when the bind occurs, the message['type'] is empty and makes the method update_transmitted_message() crash.

How to reproduce:
Try to bind with Connection(..., collect_usage=True, ...).
Here is how I init my connection object:

self.connection = Connection(self.server,
                                 auto_bind=True,
                                 client_strategy=SYNC,
                                 user=username,
                                 password=password,
                                 authentication=SIMPLE,
                                 check_names=True,
                                 collect_usage=True)

It would crash in usage.py on line 161, at the first if:

 def update_transmitted_message(self, message, length):
        self.bytes_transmitted += length
        self.operations += 1
        self.messages_transmitted += 1
        if message['type'] == 'abandonRequest':      <---- crashes because message is None
            self.abandon_operations += 1
...

It is possible to quick fix by putting the IFs in a try/catch block, but it doesn't fix the issue that message['type'] isn't set for bind requests.

Falsy defaults in AttrDef are not used

Default values for an AttrDef that are falsy, will not be used. For example:

user = ObjectDef()
user += AttrDef('memberOf', key='groups', default=[])

# Load an user entry that has no "memberOf" attribute with a reader
# If you now access the "groups" attribute of the entry ...
entry.groups

# ... you get
ldap3.core.exceptions.LDAPKeyError: 'key not found'

The problem is the test for default values in reader module.

I can not get SASL/EXTERNAL working

These are my first steps to ldap3. I have an OpenLDAP server that accepts StartTLS on port 389.

want to use SSL-certs to authz-to to a special user. This all works perfectly with python-ldap in python2.7. Now that I try to port some software to python3.4, I wanted to use ldap3.

Here is what I tried so far:


!/usr/bin/python3

import ssl

from ldap3 import Server, Connection, Tls, AUTH_SASL, SUBTREE

tls_configuration = Tls(local_private_key_file='/etc/ssl/private/mx.roessner-net.de.key',
local_certificate_file='/etc/ssl/certs/mx.roessner-net.de.crt',
ca_certs_file='/etc/ssl/certs/ca-certificates.crt',
validate=ssl.CERT_REQUIRED,
version=ssl.PROTOCOL_TLSv1)

server = Server('db.roessner-net.de',
tls=tls_configuration)

conn = Connection(server,
authentication=AUTH_SASL,
sasl_mechanism='EXTERNAL',
sasl_credentials=())

conn.open()
conn.start_tls()

bind = conn.do_sasl_bind(controls=None)
print("bind: %s" % str(bind))

i_am = conn.extend.standard.who_am_i()
print(i_am)

conn.search(search_base='ou=people,ou=it,dc=roessner-net,dc=de',
search_filter='(objectclass=*)',
attributes=['cn'],
search_scope=SUBTREE)

print(conn)

print(conn.entries)

Trying to use this, I receive an error:

bind: {'type': 'bindResponse', 'dn': '', 'saslCreds': b'', 'referrals': None, 'message': 'SASL(0): successful result: ', 'description': 'saslBindInProgress', 'result': 14}
Traceback (most recent call last):
File "./ldap-test.py", line 27, in
i_am = conn.extend.standard.who_am_i()
File "/usr/lib64/python3.4/site-packages/ldap3/extend/init.py", line 53, in who_am_i
return WhoAmI(self._connection).send()
File "/usr/lib64/python3.4/site-packages/ldap3/extend/operation.py", line 57, in send
self.decode_response()
File "/usr/lib64/python3.4/site-packages/ldap3/extend/operation.py", line 69, in decode_response
raise LDAPExtensionError('extended operation error: ' + self.result['description'] + ' - ' + self.result['message'])
ldap3.core.exceptions.LDAPExtensionError: extended operation error: operationsError - SASL bind in progress

I spent hours now, but I do not get it to work. I have no idea, if I did something wrong or if I just encountered a bug. So I ask you, if you can give me a hint.

I want ldap3 for automx, which you also find as an OSS here on Github ;-)

Can't set empty group

I think I found a bug, but maybe it is user error.

I'm trying to create an add that replicates:

dn: cn=test2,ou=Groups,dc=example,dc=edu
changetype: add
objectClass: groupOfNames
objectClass: top
cn: test2
member: 

Having member: empty is valid for 389-ds which is used to create an empty groupOfNames object. I have not been able to replicate this using ldap3.

Using variations of:

.add(
            groupDn,
            object_class=['groupOfNames', 'top'],
            attributes={'cn': "test2", 'member': None},
            controls=None
        )

I've tried setting member to None, '', and []. They either cause TypeErrors or LDAP errors. Leaving the member attribute out all together causes an error because member is a required attribute.

Thoughts?

Missing support for atomic test and set operations

From reading the available docs, I cannot find out how to replace the
following python-ldap script fragment:

    c = ldap.open('ldap.example.com')
    DN = 'cn=objectcn,dc=ldap,dc=example,dc=com'
    ATT = 'cn'
    OVAL = 'testValue'
    NVAL = 'setValue'
    c.modify_s(DN, [(ldap.MOD_ADD, ATT, OVAL),
            (ldap.MOD_DELETE, ATT, NVAL)])

which would translate to the following LDIF atomic change operation:

    dn: cn=objectcn,dc=ldap,dc=example,dc=com
    changetype: modify
    delete: cn
    cn: testValue
    -
    add: cn
    cn: setValue

While the change operations are sequenced, the modify change will happen atomically on the server.

For the use of the operation as a locking primitive, see:

paged search olcSizeLimit

I think i found an issue within the PagedSearch.py
Line 67: cookie = result['controls']['1.2.840.113556.1.4.319']['value']['cookie']

Suppose the openldap server has a result limit (olcSizeLimit) of 500 and the client requests a query which yields more than 500 results. This will raise an KeyError on the given line. This is probably due to the fact that the server cleared the cookie when the client hit the hard limit of 500 results. Can this be handled some how on the client side?

Thanks a lot btw for a great library :-)

Exhaustive server pool is too exhaustive

When using a server pool with active=True and exhaust=True, if an LDAP server goes offline, it is never able to be used again. In a case where there may only be 2 servers in the pool, this is really detrimental to a stateful app, and requires the app to be restarted in order to re-negotiate connections to these LDAP servers.

To try combat this, I have attempted to use exhaust=False. With the server offline, I attempt to make an LDAP connection, however the pool ends up in a loop attempting to find an active server. The loop never breaks when the server comes back online, and leads to an endless loop of polling.

Proper user DN and password raises LDAPInvalidCredentialsResult

using python 3.4.0 with ldap3 '0.9.7.4'.

The following code raises LDAPInvalidCredentialsResult when proper user DN and password are used.

from ldap3 import Server, Connection, AUTH_SIMPLE, STRATEGY_SYNC, ALL
s = Server(HOST, port=389, get_info=ALL)
c = Connection(s, authentication=AUTH_SIMPLE, user=user_dn, password=PASSWORD, check_names=True, lazy=False, client_strategy=STRATEGY_SYNC, raise_exceptions=True)
c.open()
c.bind()

However, within the exception, 'description': 'success' seems to point to the fact that the user bind is successful, since when the password is given as incorrect, this 'success' message does not appear in the returned exception.

Correct password exception example:

ldap3.core.exceptions.LDAPInvalidCredentialsResult: LDAPInvalidCredentialsResult - 49 - invalidCredentials - [{'dn': '', 'message': '', 'type': 'bindResponse', 'result': 0, 'saslCreds': 'None', 'description': 'success', 'referrals': None}]

See the below link for more details:
http://stackoverflow.com/questions/28575359/how-to-bind-authenticate-a-user-with-ldap3-in-python3/28616485?noredirect=1#comment45568301_28616485

Not sure how to query for the remote ldap server info. I tried the following, but it didn't seem to give me much:

$ ldapsearch -LLL -h host -b "" -s base -D "DNstuff" -W "(objectclass=*)" vendorversion objectClass isGlobalCatalogReady vendorname
Enter LDAP Password:
dn:
objectClass: top
objectClass: OpenLDAProotDSE

__str__ returned non-string in class Attribute

Hi

When I print an Attribute which holds a non-string value (an int in this case) I get this error:

__str__ returned non-string (type int)

I believe the following will fix the issue:

@@ -63,7 +63,7 @@ class Attribute(object):

     def __str__(self):
         if len(self.values) == 1:
-            return self.values[0]
+            return str(self.values[0])
         else:
             return str(self.values)

accountExpires attribute returned without formatting.

I have only seen this issue with the accountExpires attribute. Upon returning from a search, it is not being converted to a datetime object like other such attributes (lastLogon, badPasswordTime, etc.).

Clearly other values are being formatted with format_ad_timestamp() but accountExpires is consistently returned as a binary string.

extend.standard.modify_password need some special authentication?

I define my connection look like this:

    self.conn = Connection(self.server,
                           user=user,
                           password=str(password),
                           authentication=AUTH_SIMPLE,
                           auto_bind=AUTO_BIND_NO_TLS)

the result of self.conn is OK:
{'result': 0, 'description': 'success', 'saslCreds': None, 'referrals': None, 'message': '', 'dn': '', 'type': 'bindResponse'}

but when i use this self.conn to modify my userPassword, it returned a different result, and the modify action failed.

        with self.conn as c:
            c.extend.standard.modify_password(user=dn,
                                              old_password=old_password,
                                              new_password=new_password)

the result looks like this:

{'dn': '', 'type': 'extendedResp', 'responseName': None, 'referrals': None, 'result': 50, 'description': 'insufficientAccessRights', 'message': '', 'responseValue': b''}

why the the value of result changes to 50? should i need something special when i do an extend request?

SSLv3 not working on python version with SSL Context

I can't get SSLv3 to work on my install. I have an LDAP that for various reasons is not TLS capable, only SSLv3.

Trying:
s = Server('ldaps.oldserver.com', port=636, use_ssl=True)
c = Connection(s, user='test', password='test', auto_bind=True)

Yields:
LDAPSocketOpenError: ('socket ssl wrapping error: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:600)',)

I then tried:
s = Server('ldaps.oldserver.com', port=636, use_ssl=True, tls=Tls(version=ssl.PROTOCOL_SSLv3)
c = Connection(s, user='test', password='test', auto_bind=True)
with the same result.

Reading into the python ssl module documentation (https://docs.python.org/dev/library/ssl.html#context-creation) shows that the default is to have SSLv3 disabled.

I tried adding the workaround in the documentation to the ldap3 code, but it still doesn't work.
In the end, I changed the code where the SSL context is created from using create_default_context to creating an SSLContext with protocol on creating and then adding the settings, though I doubt this is the correct solution.

Problem with 'not' searches

After setting up connection c:

c.search('dc=testbase', '(testattribute=true)')  # searches fine
c.search('dc=testbase', '(!(testattribute=true))')  # search throws error in pyasn1
"""
Error:
Traceback (most recent call last):
   File <file info>
    c.search('dc=testbase', "(!(testattribute=true))")
  File "/lib/python3.4/site-packages/ldap3/core/connection.py", line 568, in search
    request = search_operation(search_base, search_filter, search_scope, dereference_aliases, attributes, size_limit, time_limit, types_only, self.server.schema if self.server else None)
  File "/lib/python3.4/site-packages/ldap3/operation/search.py", line 365, in search_operation
    request['filter'] = build_filter(search_filter, schema)  # parse the searchFilter string and compile it starting from the root node
  File "/lib/python3.4/site-packages/ldap3/operation/search.py", line 312, in build_filter
    return compile_filter(parse_filter(search_filter, schema).elements[0])
  File "/lib/python3.4/site-packages/ldap3/operation/search.py", line 256, in compile_filter
    compiled_filter['notFilter'] = boolean_filter
  File "/lib/python3.4/site-packages/pyasn1/type/univ.py", line 824, in __setitem__
    self.setComponentByName(idx, value)
  File "/lib/python3.4/site-packages/pyasn1/type/univ.py", line 859, in setComponentByName
    self._componentType.getPositionByName(name),value,verifyConstraints
  File "/lib/python3.4/site-packages/pyasn1/type/univ.py", line 1093, in setComponentByPosition
    self._verifyComponent(idx, value)
  File "/lib/python3.4/site-packages/pyasn1/type/univ.py", line 848, in _verifyComponent
    raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, t))
pyasn1.error.PyAsn1Error: Component value is tag-incompatible: Not(componentType=NamedTypes(NamedType('innerNotFilter', Filter(componentType=NamedTypes(NamedType('and', And(componentType=None)), NamedType('or', Or(componentType=None)), NamedType('notFilter', Not(componentType=NamedTypes(), tagSet=TagSet((), ))), NamedType('equalityMatch', EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('substringFilter', SubstringFilter(componentType=NamedTypes(NamedType('type', AttributeDescription()), NamedType('substrings', Substrings())))), NamedType('greaterOrEqual', GreaterOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('lessOrEqual', LessOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('present', Present()), NamedType('approxMatch', ApproxMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('extensibleMatch', ExtensibleMatch(componentType=NamedTypes(OptionalNamedType('matchingRule', MatchingRule()), OptionalNamedType('type', Type()), NamedType('matchValue', MatchValue()), DefaultedNamedType('dnAttributes', DnAttributes('False')))))))))).setComponents(Filter(componentType=NamedTypes(NamedType('and', And(componentType=None)), NamedType('or', Or(componentType=None)), NamedType('notFilter', Not(componentType=NamedTypes(), tagSet=TagSet((), ))), NamedType('equalityMatch', EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('substringFilter', SubstringFilter(componentType=NamedTypes(NamedType('type', AttributeDescription()), NamedType('substrings', Substrings())))), NamedType('greaterOrEqual', GreaterOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('lessOrEqual', LessOrEqual(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('present', Present()), NamedType('approxMatch', ApproxMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue())))), NamedType('extensibleMatch', ExtensibleMatch(componentType=NamedTypes(OptionalNamedType('matchingRule', MatchingRule()), OptionalNamedType('type', Type()), NamedType('matchValue', MatchValue()), DefaultedNamedType('dnAttributes', DnAttributes('False'))))))).setComponents(None, None, None, EqualityMatch(componentType=NamedTypes(NamedType('attributeDesc', AttributeDescription()), NamedType('assertionValue', AssertionValue()))).setComponents(AttributeDescription(b'testattribute'), AssertionValue(b'true')))) vs Not(componentType=NamedTypes(), tagSet=TagSet((), ))
"""

Missing old versions on PyPI

I am regularly hitting an issue where I have a specific ldap3 version in my requirements files but these older versions keep disappearing from PyPI (ie. v0.9.7.11 was only release in March but is no longer available - https://pypi.python.org/simple/ldap3/).

This is a real pain because I have specifically pinned certain versions of ldap3 because that is what has been tested against and provide a known working environment. It means that when I come back to work on a project after no touching it for a few month I can no longer recreate the known working environment because the old versions no longer exist.

Are these old versions being actively deleted? if so can they be restored, or at a minimum can older version no longer be deleted from this point onwards.

ldap3.core.exceptions.LDAPSizeLimitExceededResult exception message too long

Hello,

Not sure if this has already been fixed or not, however with python3-ldap 0.9.6.2 I get this incredibly long exception message:

ldap3.core.exceptions.LDAPSizeLimitExceededResult: LDAPSizeLimitExceededResult - 4 - sizeLimitExceeded - None - None - searchResDone - [ < ....> ].

Where <...> lists every single result and every single attribute that was obtained before the size got too big.

This is huge.

Thanks

Opening connection to IBM TDS crashes

When opening a connection to an IBM TDS your code crashes with:
...
File "/.../site-packages/ldap3/protocol/rfc4512.py", line 80, in quoted_string_to_list
return string
IndexError: string index out of range

The problem is, that the quoted_string_to_list function is called with an empty string.
Adding something like

    if len(string) == 0:
        return string

before accessing string[0] fixes the problem.

doesn't check ssl certificate hostname

Hello,

If I try to connect using START_TLS to a server using its IP address instead of its hostname, it still works.

This is despite the fact that only the hostname is included in the certificate.

This allows man in the middle attacks because I might be trying to connect to server.example.org, but get a valid certificate from maninthemiddle.example.org instead, and I will send my credentials to the wrong server.

Ideally this needs to be configurable, as I do have some broken systems that do identify themselves with the wrong name.

This is with validate = ssl.CERT_REQUIRED

Looking at the code, it looks like it is suppose to check the certificate in the check_hostname function, so I am going to try and work out why that doesn't appear to be working (or if I am doing something wrong), and I will report back here.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.