noirello / bonsai Goto Github PK
View Code? Open in Web Editor NEWSimple Python 3 module for LDAP, using libldap2 and winldap C libraries.
License: MIT License
Simple Python 3 module for LDAP, using libldap2 and winldap C libraries.
License: MIT License
Good afternoon. I use a wonderful bonsai package with an asynchronous rpc aiomas server based on asyncio. I encountered a memory leak problem. When rpc works and LDAPClient () connect.search () is continually invoked, the system memory is noticeably consumed, without releasing it. This problem is only observed when I use the bonsai package. In the same cyclic call, for example other methods such as aiohttp.ClientSession, such a problem is not observed. The problem with the leak is well observed when calling tracemalloc. In the example, I showed what the problem is. If you use infinite call the pps method test, there is no such problem. If you call user_exist memory, it is absorbed before your eyes.
Tracemalloc before cycling call user_exist
[109] - Top 10 lines
[115] - #1: python3.6/_weakrefset.py:37: 5.2 KiB
[118] - self.data = set()
[115] - #2: logging/__init__.py:1059: 5.1 KiB
[118] - return open(self.baseFilename, self.mode, encoding=self.encoding)
[115] - #3: python3.6/_weakrefset.py:48: 4.4 KiB
[118] - self._iterating = set()
[115] - #4: python3.6/_weakrefset.py:38: 4.2 KiB
[118] - def _remove(item, selfref=ref(self)):
[115] - #5: python3.6/sre_parse.py:416: 3.8 KiB
[118] - not nested and not items))
[115] - #6: asyncio/events.py:145: 2.9 KiB
[118] - self._callback(*self._args)
[115] - #7: python3.6/sre_parse.py:112: 2.9 KiB
[118] - self.pattern = pattern
[115] - #8: python3.6/sre_parse.py:623: 2.8 KiB
[118] - subpattern[-1] = (MAX_REPEAT, (min, max, item))
[115] - #9: python3.6/_weakrefset.py:84: 2.7 KiB
[118] - self.data.add(ref(item, self._remove))
[115] - #10: rpc-server.py:211: 2.1 KiB
[118] - class RPCServer(Test, UserGroup):
[123] - 288 other: 115.1 KiB
[125] - Total allocated size: 151.3 KiB
Tracemalloc reading after 20 min of cyclic call
[109] - Top 10 lines
[115] - #1: bonsai/ldapentry.py:18: 69086.4 KiB
[118] - super().__init__(str(dn), conn)
[115] - #2: python3.6/socket.py:489: 55269.7 KiB
[118] - a = socket(family, type, proto, a.detach())
[115] - #3: python3.6/socket.py:490: 55269.2 KiB
[118] - b = socket(family, type, proto, b.detach())
[115] - #4: python3.6/selectors.py:32: 27604.6 KiB
[118] - if isinstance(fileobj, int):
[115] - #5: python3.6/selectors.py:457: 6151.9 KiB
[118] - ready.append((key, events & key.events))
[115] - #6: python3.6/selectors.py:445: 3167.6 KiB
[118] - fd_event_list = self._epoll.poll(timeout, max_ev)
[115] - #7: bonsai/ldapconnection.py:101: 1283.8 KiB
[118] - return self._evaluate(super().open(), timeout)
[115] - #8: python3.6/_weakrefset.py:84: 563.7 KiB
[118] - self.data.add(ref(item, self._remove))
[115] - #9: python3.6/linecache.py:137: 555.1 KiB
[118] - lines = fp.readlines()
[115] - #10: asyncio/locks.py:221: 218.1 KiB
[118] - self._waiters = collections.deque()
[123] - 399 other: 2285.5 KiB
[125] - Total allocated size: 221455.4 KiB
Here are the system readings before launch
VIRT RES SHR
155М 29388 10192
Here are the system readings after half an hour of work
VIRT RES SHR
978М 586M 10696
Нere is a simple server :
def display_top(snapshot, key_type='lineno', limit=10):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics(key_type)
tracelog.info("------- Trace --------")
tracelog.info("Top %s lines" % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
# replace "/path/to/module/file.py" with "module/file.py"
filename = os.sep.join(frame.filename.split(os.sep)[-2:])
tracelog.info("#%s: %s:%s: %.1f KiB"
% (index, filename, frame.lineno, stat.size / 1024))
line = linecache.getline(frame.filename, frame.lineno).strip()
if line:
tracelog.info(' %s' % line)
other = top_stats[limit:]
if other:
size = sum(stat.size for stat in other)
tracelog.info("%s other: %.1f KiB" % (len(other), size / 1024))
total = sum(stat.size for stat in top_stats)
tracelog.info("Total allocated size: %.1f KiB" % (total / 1024))
tracelog.info("-------- End ---------")
class RPCServer(Test, UserGroup):
def __init__(self, lp, ldap):
ldap_conf.update(ldap)
ldap_url = "ldap://{}:{}".format(ldap_conf['host'],
ldap_conf['port'])
self.ldap_cli = LDAPClient(ldap_url)
self.ldap_cli.set_credentials("SIMPLE",
(ldap_conf['user'], ldap_conf['pswd']))
router = aiomas.rpc.Service()
@aiomas.expose
async def test(self, args):
async with ClientSession() as session:
try:
with timeout(9999):
async with session.get('http://python.org/') as r:
await r.text()
logging.info('test -->')
return dict(status=True, info='ok')
except BaseException as e:
return dict(status=False, info=str(e))
finally:
logging.info('test <--')
@aiomas.expose
async def trace(self):
try:
logging.info('trace -->')
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
await asyncio.sleep(0.1)
except BaseException as b:
tracelog.error("Error: {}".format(b))
finally:
logging.info('trace <--')
return dict(status=True, info='Done')
@aiomas.expose
async def user_exist(self, args):
logging.info('--> user_exist')
request = args.get('request')
s_scope = args.get('s_scope', 2)
base_dn = args.get('base_dn', 'dc=test,dc=wt')
if not request:
logging.info('<-- Required arguments: request')
return dict(status=False, info='Required arguments: request')
async with self.ldap_cli.connect(is_async=True,
timeout=5) as conn:
res = await conn.search(base_dn, s_scope, request)
logging.info('<-- Res: {}'.format(res))
if res:
return dict(status=True, info=res)
return dict(status=False, info='User does not exist')
if __name__ == '__main__':
logging.info(' *** Begin *** ')
loop = asyncio.get_event_loop()
serv = RPCServer(lp=loop, ldap=conf['ldap'])
try:
server_rpc = aiomas.run(
aiomas.rpc.start_server(
('127.0.0.1', 5000),
serv,
),
)
aiomas.run(until=server_rpc.wait_closed())
except BaseException as servexpt:
logging.info('Core server brake: {}'.format(servexpt))
Client :
async def user_exist(user):
rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
res = await rpc_con.remote.user_exist(args=dict(base_dn='....',request='cn=...'))
await rpc_con.close()
return res
async def test():
rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
res = await rpc_con.remote.call(method='test', args=dict(verbosity=1))
await rpc_con.close()
return res
async def trace():
rpc_con = await aiomas.rpc.open_connection(('127.0.0.1', 5000))
res = await rpc_con.remote.trace()
await rpc_con.close()
return res
async def get_result(tasks):
for i in asyncio.as_completed(tasks):
res = await i
print("Response: {}".format(res))
user = '52fce218-ba2d-46d9-bda2-0d166a6af80d'
users = ['','','','']
loop = asyncio.get_event_loop()
while True:
coros = [user_exist(i) for i in users]
loop.run_until_complete(get_result(coros))
hey,
i have two different AD REALMS, one of them will only return exactly 1000 results for paged searches. where the other will return me way more results.
i already talked to the AD maintainers, and they cannot find an issue.
also ldapsearch will return the right result.
i also tried the "ldap3" python client. this client also returns the correct number of results.
any clue what is going on here?
here is some example code:
ldap3:
from ldap3 import Server, Connection
import ldap3
server = Server('dcexample.com', use_ssl=False, get_info=ldap3.ALL)
conn = Connection(server, user='[email protected]', password='password', auto_bind=True)
entries = conn.extend.standard.paged_search('dc=example,dc=com', '(objectClass=group)', attributes=['cn'], paged_size=1000)
results = list()
for entry in entries:
results.append(entries)
print(len(results))
bonsai
import bonsai
client = bonsai.LDAPClient('ldap://dc.example.com')
client.set_credentials('SIMPLE', user='[email protected]', password='password')
conn = client.connect()
groups = conn.paged_search('DC=example,DC=com', bonsai.LDAPSearchScope.SUBTREE, "(objectClass=user)", page_size=1000)
results = []
for i in groups:
results.append(i)
print(len(results))
i guess there is a configuration difference between the two AD realms, but since "ldapsearch" and the "ldap3" client return the right number of results, i feel like bonsai is doing something wrong here.
It's protocol extension is very useful for query the user with his list of security groups GUID in a single request
More info: https://msdn.microsoft.com/en-us/library/aa366980(v=vs.85).aspx
http://ldap3.readthedocs.io/ldap3.protocol.microsoft.html#ldap3.protocol.microsoft.ExtendedDN
Hi,
I'm willing to believe the assertion that ldapdn.py only deals in valid DNs, and the unstated implication that DNs with whitespace in them may not be technically valid, however some records at my site have this affliction, and they seem to work fine with (almost) all other LDAP tooling I've used. I don't know if it's worth adding an option to permit this in bonsai, but incase anybody else finds themselves in this situation, one naive solution is to just remove whitespace from strdn
at the top of LDAPDN
's __init__
method in ldapdn.py, e.g.:
def __init__(self, strdn: str) -> None:
+ strdn = strdn.replace(" ", "")
if strdn != "" and not self._dnregex.match(strdn):
raise InvalidDN(strdn)
When installing with python 3.6, everything went fine. But when we upgraded to python 3.9, installing bonsai resulted in the message
error: microsoft visual c++ 14.0 or greater is required
Installing this on my workstation allowed the needed parts to get compiled and bonsai installed successfully. But our company does not allow installing development tools on production servers, so we will need a binary to deploy to our server environment.
Can you add the required wheel binaries to the pip package for python 3.9?
I found bonsai recently and I am trying to change the LDAP searching part in my project from ldap3 to bonsai since ldap3 is too slow especially establishing the first connection.
I need to search many data at the same time, so I wonder what is the recommended way to do this concurrent by using bonsai?
I worked out a coroutine way and a thread way:
# use coroutine
async def search(pool, mail):
print(f'start search {mail}...')
con = await pool.get()
result = await con.search(
BASE, bonsai.LDAPSearchScope.SUBTREE,
f'(mail={mail})', attrlist=['uid']
)
await pool.put(con)
print(f'end search {mail}!')
return result
async def main():
ml = ['[email protected]', '[email protected]', '[email protected]']
client = bonsai.LDAPClient(LDAP_URL)
pool = AIOConnectionPool(client, minconn=1, maxconn=5)
await pool.open()
rl = await asyncio.gather(*[search(pool, m) for m in ml])
return rl
loop = asyncio.get_event_loop()
result = loop.run_until_complete(main())
# use thread
client = bonsai.LDAPClient(LDAP_URL)
pool = bonsai.pool.ThreadedConnectionPool(client, 1, 5)
pool.open()
def search(mail):
print(f'start search {mail}...')
con = pool.get()
result = con.search(
BASE, bonsai.LDAPSearchScope.SUBTREE,
f'(mail={mail})', attrlist=['uid']
)
pool.put(con)
print(f'end search {mail}!')
return result
with multiprocessing.pool.ThreadPool(5) as p:
result = p.map(search, ml)
but I am not sure which one is better and whether there is a better way than both these two.
https://msdn.microsoft.com/en-us/library/cc223811.aspx
Ntver option is a byte string. When searching for the form:
conn.search(base='', scope=bonsai.LDAPSearchScope.BASE, filter='(&(DnsDomain=EKB2003.LOCAL)(NtVer=\00\00\00\01))', attrlist=['NETLOGON'])
I get the error:
TypeError: Wrong parameters (base<str|LDAPDN>, scope<int>, filter<str>, attrlist<List>, timeout<float>, attrsonly<bool>, sort_order<List>, page_size<int>, offset<int>, before_count<int>, after_count<int>, est_list_count<int>, attrvalue<object>).
How to fix it?
How do I connect to a trusting domain in different forest? Is there any guidance around it?
I tried to connect to the trusting domain by providing the credentials of my base domain. My user account is created in "Domain_1" which is under "Master_Domain_1". I can authenticate to both these domains. I have Master_Domain_2 which is a separate forest that has "Domain_2" domain.
import bonsai
import configparser
bonsai.set_connect_async(False)
CACERT_FILE = "./Cert/cacerts.pem"
username = 'my_user'
password = 'SecretPassword#'
config = configparser.ConfigParser()
config.read('.//AD_Configs//Config.ini')
server_name = config['DOMAIN_1']['URL']
client = bonsai.LDAPClient(server_name)
client.set_ca_cert(CACERT_FILE)
client.set_auto_page_acquire(True)
client.set_credentials("SIMPLE", user="CN="+username+"OU=Users,DC=DOMAIN_1,DC=com", password=password)
base = config['DOMAIN_1']['Base']
# bonsai.set_connect_async(False)
conn = client.connect()
query = "(&(objectClass=group)(SamAccountName=Domain Admins))"
list_of_attributes = ['*']
final_result = []
with client.connect() as conn:
result = conn.paged_search(base=base, scope=2, filter_exp=query, attrlist=['*'],
page_size=1000, attrsonly=False)
for r in result:
final_result.append(r)
print(len(final_result))
msgid = result.acquire_next_page()
while msgid is not None:
result = conn._evaluate(msgid)
for r in result:
final_result.append(r)
msgid = result.acquire_next_page()
print(len(final_result))
This code is successful when I change the DOMAIN_1 to Master_Domain_1 as well. When I change the server_name
to reference the Domain_2 or Master_Domain_2 (also changed the base variable) where I have bi-directional trust I am not able to authenticate and I get the following error for conn = client.connect()
-
bonsai.errors.AuthenticationError: Invalid credentials. 80090308: LdapErr: DSID-0C090447, comment: AcceptSecurityContext error, data 52e, v3839 (0x0031 [49])
The debug logs are -
DBG: ldapconnection_new [self:0x11790b760]
DBG: ldapconnection_init (self:0x11790b760)
DBG: ldapconnection_open (self:0x11790b760)
DBG: connecting (self:0x11790b760)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x117905600)
DBG: ldapconnectiter_new [self:0x117916300]
DBG: create_init_thread_data (client:0x1178f6c40, sock:-1)
DBG: create_init_thread (ld:0x7fbba9ae51b0, info:0x7fbba9ae5b60, thread:0)
DBG: ldapconnection_result (self:0x11790b760, args:0x1162f0300, kwds:0x0)[msgid:-1]
DBG: LDAPConnection_Result (self:0x11790b760, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x117916300, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:123145512566784, timeout:-1, misc:0x7fbba9ae51b0)
DBG: ldap_init_thread_func (params:0x7fbba9ae51b0)
DBG: _pthread_mutex_timedlock
DBG: set connecting async: 0
DBG: ldap_init_thread_func [retval:0]
ldap_create
ldap_url_parse_ext(ldaps://domain_2.com:636)
DBG: set_certificates (self:0x117916300)
DBG: binding [state:3]
ldap_sasl_bind
DBG: _ldap_bind (ld:0x7fbba9a77110, info:0x7fbba9ae5b60, ppolicy:0, result:0x0, msgid:0)
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP domain_2.com:636
ldap_new_socket: 7
ldap_prepare_socket: 7
ldap_connect_to_host: Trying 10.210.21.193:636
ldap_pvt_connect: fd: 7 tm: -1 async: 0
attempting to connect:
connect success
TLS trace: SSL_connect:before SSL initialization
TLS trace: SSL_connect:SSLv3/TLS write client hello
TLS trace: SSL_connect:SSLv3/TLS write client hello
TLS trace: SSL_connect:SSLv3/TLS read server hello
TLS certificate verification: depth: 3, err: 0, subject:
TLS certificate verification: depth: 2, err: 0, subject:
TLS certificate verification: depth: 1, err: 0, subject:
TLS certificate verification: depth: 0, err: 0, subject:
TLS trace: SSL_connect:SSLv3/TLS read server certificate
TLS trace: SSL_connect:SSLv3/TLS read server key exchange
TLS trace: SSL_connect:SSLv3/TLS read server certificate request
TLS trace: SSL_connect:SSLv3/TLS read server done
TLS trace: SSL_connect:SSLv3/TLS write client certificate
TLS trace: SSL_connect:SSLv3/TLS write client key exchange
TLS trace: SSL_connect:SSLv3/TLS write change cipher spec
TLS trace: SSL_connect:SSLv3/TLS write finished
TLS trace: SSL_connect:SSLv3/TLS write finished
TLS trace: SSL_connect:SSLv3/TLS read change cipher spec
TLS trace: SSL_connect:SSLv3/TLS read finished
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
DBG: LDAPConnectIter_Next (self:0x117916300, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
ldap_result ld 0x7fbba9a77110 msgid 1
wait4msg ld 0x7fbba9a77110 msgid 1 (infinite timeout)
wait4msg continue ld 0x7fbba9a77110 msgid 1 all 1
** ld 0x7fbba9a77110 Connections:
* host: domain_2.com port: 636 (default)
refcnt: 2 status: Connected
last used: Tue Aug 10 14:54:56 2021
** ld 0x7fbba9a77110 Outstanding Requests:
* msgid 1, origid 1, status InProgress
outstanding referrals 0, parent count 0
ld 0x7fbba9a77110 request count 1 (abandoned 0)
** ld 0x7fbba9a77110 Response Queue:
Empty
ld 0x7fbba9a77110 response count 0
ldap_chkResponseList ld 0x7fbba9a77110 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fbba9a77110 NULL
ldap_int_select
read1msg: ld 0x7fbba9a77110 msgid 1 all 1
read1msg: ld 0x7fbba9a77110 msgid 1 message type bind
read1msg: ld 0x7fbba9a77110 0 new referrals
read1msg: mark request completed, ld 0x7fbba9a77110 msgid 1
request done: ld 0x7fbba9a77110 msgid 1
res_errno: 49, res_error: <80090308: LdapErr: DSID-0C090447, comment: AcceptSecurityContext error, data 52e, v3839>, res_matched: <>
ldap_free_request (origid 1, msgid 1)
ldap_parse_result
ldap_msgfree
ldap_err2string
DBG: ldapconnectiter_dealloc (self:0x117916300)
DBG: dealloc_conn_info (info:0x7fbba9ae5b60)
DBG: ldapconnection_dealloc (self:0x11790b520)
ldap_free_connection 1 1
ldap_send_unbind
TLS trace: SSL3 alert write:warning:close notify
ldap_free_connection: actually freed
What should I do differently ? I kept the user identity to my original domain_1 id and password as I use the same to authenticate with Powershell. (but RSAT module code be doing something in the background)
Hi,
i have found some misttakes in the parsing regex inside LDAPURL.__str2url().
The expression to split URLs is (using re.VERBOSE):
r"""
^
(ldap[s|i]?) # (1: scheme)
://
( # (2:
([^:/?]*)? # (3: host)
( # (4
[:] # ':'
([1-9][0-9]{0,4}) # (5: port)
)? # )?
| # --OR--
[\[]? # '['?
([^/?\]]*) # (6: host)
( # (7:
[\]] # ']'
[:] # ':'
([1-9][0-9]{0,4}) # (8: port)
)? # )
) # )
[/]?
([^\]/:?]*)? # (9: bind DN)
[\?]?
([^\]:?]*)? # (10: attributes)
[\?]?
([^\]:?]*)? # (11: scope)
[\?]?
([^\]:?]*)? # (12: filter)
[\?]?
([^\]:?]*)? # (13: extensions)
$
"""
So you can have a scheme of "ldap", "ldaps", "ldap|" or "ldapi".
I think you mean (ldap[si]?) instead?
The server part (group 2) matches i.e. on
(empty string)
host
[host # Error
host:99
host]:99 # Error
[host]:99
:99
]:99 # Error
[]:99 # Error?
But never ever you can have a "[host]" without specifying port number (because the closing bracket is part of group 7).
Additionally, the separator between the server part (group 2) and the possibly following base-dn (group 9) can be omitted. But that can't work.
My suggestion is to use urlpase or urlsplit from urllib.parse - see urllib.parse — Split URLs into Components
To do this you need to extend the following class variables of urllib.parse with the ldap schemes {'ldap', 'ldaps', 'ldapi'}:
urllib.parse.uses_netloc
urllib.parse.uses_query
urllib.parse.uses_params # maybe
Btw: is_valid_hostname:
To parse or check any raw ip addresses use ipaddress.ip_address. It returns an address object (IPv4Address or IPv6Address) with a version attribute showing a value of 4 or 6 respectively.
In an unstable network environment, once the network down, the LDAP search will never success even after the network connection is back. Is there an auto reconnect flag or some other ways to make searches work after the network is restored?
import bonsai
client = bonsai.LDAPClient(ldap_url)
conn = client.connect(timeout=3)
conn.search(base, scope, filter, timeout=3) # here it works
# here the network was gone
conn.search(base, scope, filter, timeout=3) # timeout error, of course
# here the network restored
conn.search(base, scope, filter, timeout=3) # still timeout error! I hope it can work here.
print(conn.closed) # it is False here, so I cannot manually reconnect it with a check of close status
Not sure if I'm doing something wrong or this is a bug. I can't get tls turned on with LDAPClient.
>>> from bonsai import LDAPClient
>>> LDAPClient('ldaps://localhost').tls
False
>>> LDAPClient('ldaps://localhost', tls=True).tls
False
This is with bonsai==1.1.0
Even with auto next page, it seems to be limited at 1000, acquire_next_page even returns None.
Python on Windows 10 running against an AD search is expected to return 1020 results 😓
I've update bonsai from 0.9.1 to 1.0.0 and it has started to get connection error "bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])" every time, no matter if I set credentials / certificate options or not. Debug output:
client = bonsai.LDAPClient("ldaps://my.ldap:636")
client.set_credentials('simple', user='ou=condn', password='password')
conn = client.connect()
DBG: ldapconnection_new [self:0x7fd9c87fdd08]
DBG: ldapconnection_init (self:0x7fd9c87fdd08)
DBG: ldapconnection_open (self:0x7fd9c87fdd08)
DBG: connecting (self:0x7fd9c87fdd08)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7fd9af8f8240)
DBG: ldapconnectiter_new [self:0x7fd9af8f8f18]
DBG: create_init_thread_data (client:0x7fd9ae68d940, sock:-1)
DBG: create_init_thread (ld:0x29145e0, info:0x2913460, thread:0)
DBG: ldapconnection_result (self:0x7fd9c87fdd08, args:0x7fd9ae6940c8, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7fd9c87fdd08, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
ldap_create
ldap_url_parse_ext(ldaps://my.ldap:636)
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP my.ldap:636
ldap_new_socket: 19
ldap_prepare_socket: 19
ldap_connect_to_host: Trying 10.0.0.1:636
ldap_pvt_connect: fd: 19 tm: 0 async: -1
ldap_ndelay_on: 19
attempting to connect:
connect errno: 115
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
ldap_result ld 0x7fd9b40076d0 msgid 1
wait4msg ld 0x7fd9b40076d0 msgid 1 (infinite timeout)
wait4msg continue ld 0x7fd9b40076d0 msgid 1 all 1
** ld 0x7fd9b40076d0 Connections:
* host: my.ldap port: 636 (default)
refcnt: 2 status: Connected
last used: Fri Jan 25 16:11:58 2019
** ld 0x7fd9b40076d0 Outstanding Requests:
* msgid 1, origid 1, status Writing
outstanding referrals 0, parent count 0
ld 0x7fd9b40076d0 request count 1 (abandoned 0)
** ld 0x7fd9b40076d0 Response Queue:
Empty
ld 0x7fd9b40076d0 response count 0
ldap_chkResponseList ld 0x7fd9b40076d0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fd9b40076d0 NULL
ldap_int_select
wait4msg continue ld 0x7fd9b40076d0 msgid 1 all 1
** ld 0x7fd9b40076d0 Connections:
* host: my.ldap port: 636 (default)
refcnt: 2 status: Connected
last used: Fri Jan 25 16:11:58 2019
** ld 0x7fd9b40076d0 Outstanding Requests:
* msgid 1, origid 1, status InProgress
outstanding referrals 0, parent count 0
ld 0x7fd9b40076d0 request count 1 (abandoned 0)
** ld 0x7fd9b40076d0 Response Queue:
Empty
ld 0x7fd9b40076d0 response count 0
ldap_chkResponseList ld 0x7fd9b40076d0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7fd9b40076d0 NULL
ldap_int_select
DBG: ldap_init_thread_func (params:0x29145e0)
DBG: set_cert_policy (ld:0x7fd9b40076d0, cert_policy:4)
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140573321987840, timeout:-1, misc:0x29145e0)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7fd9af8f8f18)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7fd9b40076d0, info:0x2913460, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7fd9af8f8f18, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7fd9af8f8f18)
DBG: dealloc_conn_info (info:0x2913460)
read1msg: ld 0x7fd9b40076d0 msgid 1 all 1
ldap_msgfree
ldap_err2string
Traceback (most recent call last):
File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2961, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-29-a00ee1f64c39>", line 1, in <module>
conn = client.connect()
File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapclient.py", line 577, in connect
return LDAPConnection(self).open(timeout)
File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 292, in open
return super().open(timeout)
File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 53, in open
return self._evaluate(super().open(), timeout)
File "/home/pkirillov/.virtualenvs/amp/lib/python3.6/site-packages/bonsai/ldapconnection.py", line 241, in _evaluate
return self.get_result(msg_id, timeout)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])
And debug output from 0.9.1 version:
client = bonsai.LDAPClient("ldaps://my.ldap:636")
client.set_credentials('simple', ('ou=conndn', 'password'))
conn = client.connect()
DBG: ldapconnection_new [self:0x7f9e1a63bd08]
DBG: ldapconnection_init (self:0x7f9e1a63bd08)
DBG: ldapconnection_open (self:0x7f9e1a63bd08)
DBG: connecting (self:0x7f9e1a63bd08)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7f9e084eed48)
DBG: ldapconnectiter_new [self:0x7f9e0973c9c0]
DBG: create_init_thread_data (client:0x7f9e09781080, sock:-1)
DBG: create_init_thread (ld:0x2323cf0, info:0x2014a00, thread:0)
DBG: ldapconnection_result (self:0x7f9e1a63bd08, args:0x7f9e084f08c8, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7f9e1a63bd08, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f9e0973c9c0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140316997838592, timeout:-1, misc:0x2323cf0)
DBG: ldap_init_thread_func (params:0x2323cf0)
DBG: set_cert_policy (ld:0x7f9e0400fcd0, cert_policy:4)
DBG: ldap_init_thread_func [retval:0]
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7f9e0973c9c0)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7f9e0400fcd0, info:0x2014a00, ppolicy:0, result:(nil), msgid:0)
ldap_create
ldap_url_parse_ext(ldaps://my.ldap:636)
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP my.ldap:636
ldap_new_socket: 10
ldap_prepare_socket: 10
ldap_connect_to_host: Trying 10.0.0.1:636
ldap_pvt_connect: fd: 10 tm: -1 async: 0
attempting to connect:
connect success
ldap_open_defconn: successful
ldap_send_server_request
ldap_msgfree
ldap_result ld 0x7f9e0400fcd0 msgid 1
wait4msg ld 0x7f9e0400fcd0 msgid 1 (infinite timeout)
wait4msg continue ld 0x7f9e0400fcd0 msgid 1 all 1
** ld 0x7f9e0400fcd0 Connections:
* host: my.ldap port: 636 (default)
refcnt: 2 status: Connected
last used: Fri Jan 25 16:30:09 2019
** ld 0x7f9e0400fcd0 Outstanding Requests:
* msgid 1, origid 1, status InProgress
outstanding referrals 0, parent count 0
ld 0x7f9e0400fcd0 request count 1 (abandoned 0)
** ld 0x7f9e0400fcd0 Response Queue:
Empty
ld 0x7f9e0400fcd0 response count 0
ldap_chkResponseList ld 0x7f9e0400fcd0 msgid 1 all 1
ldap_chkResponseList returns ld 0x7f9e0400fcd0 NULL
ldap_int_select
read1msg: ld 0x7f9e0400fcd0 msgid 1 all 1
read1msg: ld 0x7f9e0400fcd0 msgid 1 message type bind
read1msg: ld 0x7f9e0400fcd0 0 new referrals
read1msg: mark request completed, ld 0x7f9e0400fcd0 msgid 1
request done: ld 0x7f9e0400fcd0 msgid 1
res_errno: 0, res_error: <>, res_matched: <>
ldap_free_request (origid 1, msgid 1)
ldap_parse_result
ldap_msgfree
DBG: LDAPConnectIter_Next (self:0x7f9e0973c9c0, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7f9e0973c9c0)
DBG: dealloc_conn_info (info:0x2014a00)
DBG: ldapconnection_dealloc (self:0x7f9e18d570a8)
ldap lib versions:
Ubuntu 18.10: libldap-2.4-2/cosmic-updates,now 2.4.46+dfsg-5ubuntu1.1 amd64
Centos 7.5: openldap-2.4.44-15.el7_5.x86_64
We are using bonsai to authenticate OpenVPN-users via a Python-Flask-Webservice.
We are using:
bonsai, v1.2.0
installed via piplibldap-2.4-2, v2.4.49+dfsg-2ubuntu1.5
libldap2-dev, v2.4.49+dfsg-2ubuntu1.5
For the authentication we have the following function:
def LDAPAuthenticate(username, password):
client = LDAPClient("ldapi://%2Frun%2Fslapd%2Fldapi")
client.set_credentials("SIMPLE", user=f"uid={username},cn=people,dc=domain.org", password=password)
session = False
try:
client.connect():
except AuthenticationError as err:
log_error(f"LDAP-Authentication for '{username}' failed: {err}")
except Exception as e:
log_error(f"LDAP-Service failed: {e}")
else:
log_debug(f"LDAP-Authentication for '{username}' succeeded.")
session = True
return session
When a users enters a wrong password within this authentication, every call to LDAPAuthenticate() leaks around 14 filedescriptors on the ldapi-Socketconnection, which gets not closed after the connect()-call. These connections are only closed, when the python3-webservice process exits or the slapd gets restarted.
When this wrong authentication is done a few hundred times (as some of our users have misconfigured clients), the ldap-daemon stops responding at all, as the number of maximum open filedescriptors is exhausted.
We have installed a workaround to restart the gunicorn-Webservice-Processes after 100 Requests. That we can not reach the fd-limit on the slapd.
The connection-debug of a failed connection (using bonsai.set_debug(True)
):
DBG: ldapconnection_new [self:0x7ff8b0ad0340]
DBG: ldapconnection_init (self:0x7ff8b0ad0340)
DBG: ldapconnection_open (self:0x7ff8b0ad0340)
DBG: connecting (self:0x7ff8b0ad0340)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7ff8b1dbcc40)
DBG: ldapconnectiter_new [self:0x7ff8b0ad6440]
DBG: create_init_thread_data (client:0x7ff8b1da1a00, sock:-1)
DBG: create_init_thread (ld:0x151a2e0, info:0x151a290, thread:0)
DBG: ldapconnection_result (self:0x7ff8b0ad0340, args:0x7ff8b0ad9c00, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7ff8b0ad0340, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:0]
DBG: ldap_init_thread_func (params:0x151a2e0)DBG: _ldap_finish_init_thread (async:0, thread:140706090530560, timeout:-1, misc:0x151a2e0)
DBG: _pthread_mutex_timedlock
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140706090530560, timeout:-1, misc:0x151a2e0)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7ff8b0ad6440)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7ff8ac000b60, info:0x151a290, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7ff8b0ad6440, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7ff8b0ad6440)
DBG: dealloc_conn_info (info:0x151a290)
LDAP-Authentication for 'senfomat' failed: Invalid credentials. (0x0031 [49])
DBG: ldapconnection_dealloc (self:0x7ff8b0ad0340)
The connection with a working auth looks like this:
DBG: ldapconnection_new [self:0x7fb50112b340]
DBG: ldapconnection_init (self:0x7fb50112b340)
DBG: ldapconnection_open (self:0x7fb50112b340)
DBG: connecting (self:0x7fb50112b340)
DBG: create_conn_info (mech:SIMPLE, sock:-1, creds:0x7fb502417c40)
DBG: ldapconnectiter_new [self:0x7fb5011314e0]
DBG: create_init_thread_data (client:0x7fb502309b50, sock:-1)
DBG: create_init_thread (ld:0x1e34740, info:0x1e51290, thread:0)
DBG: ldapconnection_result (self:0x7fb50112b340, args:0x7fb501134b40, kwds:(nil))[msgid:-1]
DBG: LDAPConnection_Result (self:0x7fb50112b340, msgid:-1, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140415381595904, timeout:-1, misc:0x1e34740)
DBG: _pthread_mutex_timedlock
DBG: ldap_init_thread_func (params:0x1e34740)
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:0, thread:140415381595904, timeout:-1, misc:0x1e34740)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7fb5011314e0)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7fb4fc002fc0, info:0x1e51290, ppolicy:0, result:(nil), msgid:0)
DBG: LDAPConnectIter_Next (self:0x7fb5011314e0, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnectiter_dealloc (self:0x7fb5011314e0)
DBG: dealloc_conn_info (info:0x1e51290)
DBG: ldapconnection_dealloc (self:0x7fb50112b340)
LDAP-Authentication for 'senfotest' succeeded.
We are not sure, why the bonsai-Module is doing the connection in async mode. We do not want this, we left it on the default of False
. But as the debuglog shows in the middle, the module is forcing it to async.
I've been trying to do an asynchronous LDAP-search with PyLDAP. But for the app I would like to make, there is already an eventloop (which is not based python3.4's asyncio). I could not see a way how to do asynchronous operations without having an asyncio-eventloop.
While I think it is a very nice idea to support asyncio's yield from
pattern from a ldap-module, I would consider it a "bug" if it is not possible to do asynchronous operations without it.
[edit] Put quotes around "bug". [/edit]
Any chance of making it one? Its takes a non-trivial amount of time and if the loop could be doing things while waiting on network things that would be great.
Good work by the way.
Hi,
thank you for developing bonsai, the API is much nicer than python-ldap. I have a problem that when I create a new user with bonsai via AD, then I cannot set the userAccountControl
attribute to enable the user:
entry = LDAPEntry(user.distinguished_name)
entry["cn"] = user.fullname
entry["givenName"] = user.firstname
entry["sn"] = user.lastname
entry["userPrincipalName"] = user.account_name
entry["displayName"] = user.fullname
entry["objectclass"] = "user",
entry["sAMAccountName"] = user.account_name
entry["userPassword"] = user.password
entry["mail"] = user.email
with self._build_client().connect() as conn:
res = conn.add(entry)
print(res)
# http://www.selfadsi.de/ads-attributes/user-userAccountControl.htm
entry.change_attribute("userAccountControl", LDAPModOp.REPLACE, 66048 )
entry.modify()
The account is created but the modify
fails:
bonsai.errors.UnwillingToPerform: Unwilling To Perform. (0x0035 [53])
Do you have an idea what is wrong? I checked already the password, it adheres the AD policies and I also tried setting 512
instead of 66048
. I also tried setting it in the initial create, but the error is then the same.
I am trying to get all attributes related to users so when I use attrlist=['*']
in the paged search I get about 125 attributes. When I tried the Get-ADUser in Powershell to to enumerate all the attributes there are about 205. I am trying to get the remaining attributes as well is there a way to do that? The missing attributes include -
AccountNotDelegated, AllowReversiblePasswordEncryption, BadLogonCount, CannotChangePassword, DoesNotRequirePreAuth etc.
example code -
with client.connect() as conn:
result = conn.paged_search(base=base, scope=2, filter_exp=query, attrlist=['*'],
page_size=1000, attrsonly=False)
Powershell code -
Get-ADUser -Server $Server -Filter $Filter -ResultPageSize 1000 -Properties *
Please, if it possible, upload wheels for linux platform, we can always build from sources, but wheel more useful.
I think connection pools do not work well with async. I may be doing something wrong of course. Here goes nothing:
import asyncio
import bonsai
client = bonsai.LDAPClient("ldap://opendj:1389")
client.set_credentials(mechanism="SIMPLE", user="cn=Directory Manager", password="password")
pool = bonsai.pool.ConnectionPool(client, minconn=1, maxconn=2, is_async=True)
pool.open()
async def test():
conn = await pool.get()
pool.put(conn)
conn = await pool.get()
loop = asyncio.get_event_loop()
result = loop.run_until_complete(test())
This results in
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-11-07b69a060a6c> in <module>
14
15 loop = asyncio.get_event_loop()
---> 16 result = loop.run_until_complete(test())
/usr/local/lib/python3.8/asyncio/base_events.py in run_until_complete(self, future)
614 raise RuntimeError('Event loop stopped before Future completed.')
615
--> 616 return future.result()
617
618 def stop(self):
<ipython-input-11-07b69a060a6c> in test()
10 conn = await pool.get()
11 pool.put(conn)
---> 12 conn = await pool.get()
13
14
RuntimeError: cannot reuse already awaited coroutine
What also makes me think that the async implementation was not designed to work with connection pools is the fact that pool.spawn
does not work at all because it is wrapped in contextlib.contextmanager
and obviously lacks __aenter__
and __aexit__
methods so it fails when used with async with
.
Any advice on how to bite this? I am designing an application for quite high throughput.
EDIT:
Okay, I think I have hacked my way around it like this
import asyncio
from contextlib import asynccontextmanager
import bonsai
class AsyncConnectionPool(bonsai.pool.ConnectionPool):
def __init__(self, *args, **kwargs):
super().__init__(is_async=True, *args, **kwargs)
@asynccontextmanager
async def spawn(self, *args, **kwargs):
try:
if self._closed:
self.open()
conn = self.get(*args, **kwargs)
if conn.closed:
await conn.open()
yield conn
finally:
self.put(conn)
client = bonsai.LDAPClient("ldap://opendj:1389")
client.set_credentials(mechanism="SIMPLE", user="cn=dm", password="cangetinds")
pool = AsyncConnectionPool(client, minconn=1, maxconn=2)
async def test():
async with pool.spawn() as conn1:
print(await conn1.whoami())
async with pool.spawn() as conn2:
print(await conn2.whoami())
async with pool.spawn() as conn1:
print(await conn1.whoami())
loop = asyncio.get_event_loop()
result = loop.run_until_complete(test())
This works fine, but now I get this ugly warning
/home/remy/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py:70: RuntimeWarning: coroutine 'AIOLDAPConnection._poll' was never awaited
self.__open_coro = super().open(timeout)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
not sure what to do with this and what the side effects are.
Hello,
I found nasty bug that freezes my application :(
Steps to reproduce:
import asyncio
import time
import bonsai
async def connect():
client = bonsai.LDAPClient(f"ldap://8.8.8.8")
async with client.connect(is_async=True, timeout=3):
return True
async def printer():
while True:
print(f"{time.time()} hi")
await asyncio.sleep(1)
async def main():
asyncio.create_task(printer())
await asyncio.sleep(3)
await connect()
asyncio.run(main())
1596881355.416916 hi
1596881356.420735 hi
1596881357.4230669 hi
Traceback (most recent call last):
File ".../script_bonsai.py", line 25, in <module>
asyncio.run(main())
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 583, in run_until_complete
return future.result()
File ".../script_bonsai.py", line 22, in main
await connect()
File ".../script_bonsai.py", line 9, in connect
async with client.connect(is_async=True, timeout=3):
File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 30, in __aenter__
return await self.__open_coro
File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 63, in _poll
raise exc
File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
return await asyncio.wait_for(fut, timeout)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
return fut.result()
File ".../lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 45, in _ready
res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])
1596881434.8999639 hi
Process finished with exit code 1
I think super().get_result(msg_id)
should be executed in run_in_executor
with wait_for
Hi, I am running into the following error with the code.
I am working with a virtual active directory connection,(Ldap3 over SSL)
Code:
compiled_search_filter = '(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=ADgroup,OU=test,OU=sys,DC=a,DC=b,DC=com))'
async with client.connect(is_async=True) as conn:
res = []
result = await conn.paged_search(base="", scope=2, filter_exp=compiled_search_filter,
attrlist=['employeeID', 'sAMAccountName','objectClass','DistinguishedName'],
timeout=10,attrsonly=True,page_size=1000)
async for r in result:
res.append(r)
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "", line 1, in
runfile('/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py', wdir='/Users/bawer/Documents/Automation Projects/Test')
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 87, in
res = loop.run_until_complete(get_members(group_name))
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 70, in get_members
attrlist=['employeeID', 'sAMAccountName','objectClass','DistinguishedName'],timeout=10,attrsonly=True,page_size=1000)
File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 64, in _poll
debug.txt
raise exc
File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 59, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
return fut.result()
File "/usr/local/lib/python3.7/site-packages/bonsai-1.2.0-py3.7-macosx-10.14-x86_64.egg/bonsai/asyncio/aioconnection.py", line 45, in _ready
res = super().get_result(msg_id)
bonsai.errors.LDAPError: Operations error. (0x0001 [1])
The query seems to work with JXplorer. It seems the connection might be timing out.
Debug logs attached
With is_async=False
I get the following error -
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "/Users/bawer/Documents/Automation Projects/Test/Bonsai_AD_Domains.py", line 23, in find_dn
async with client.connect(is_async=False) as conn:
AttributeError: aexit
Hello! I really appreciate the work you have put into this library!
I am getting an issue where if I try to enable ldaps/TLS in my code, it fails to connect to the server.
When I was using the ldap3 library it would be able to connect to the server with ldaps so I am unsure why I am having this issue.
For example, when I just have TLS enabled:
#!/usr/bin/env python3
import asyncio
import bonsai
async def do():
client = bonsai.LDAPClient(f"ldap://{remote}", True)
client.set_credentials("SIMPLE", user=username, password=passphrase)
async with client.connect(is_async=True) as conn:
who = await conn.whoami()
print(who)
loop = asyncio.get_event_loop()
loop.run_until_complete(do())
It returns this Traceback:
Traceback (most recent call last):
File "./testing.py", line 15, in <module>
loop.run_until_complete(do())
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "./testing.py", line 10, in do
async with client.connect(is_async=True) as conn:
File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 25, in __aenter__
return await self.__open_coro
File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
raise exc
File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 54, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/lib/python3.8/asyncio/tasks.py", line 455, in wait_for
return await fut
File "/home/testing/.local/lib/python3.8/site-packages/bonsai/asyncio/aioconnection.py", line 40, in _ready
res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Connect error. (unknown error code) (0xFFF5 [-11])
It shows the same results when I use ldaps.
Do you have any insight into this issue?
Hi!
I got some very strange behavior with bonsai
on macOS, first connection never works properly.
Here my code example:
import asyncio
import bonsai
@asyncio.coroutine
def do():
client = bonsai.LDAPClient('ldaps://secret')
client.set_credentials('SIMPLE', ('secret', 'secret'))
client.set_cert_policy('allow')
with (yield from client.connect(is_async=True, timeout=10.0)) as conn:
res = yield from conn.search('ou=nerdherd,dc=bonsai,dc=test', 2)
print(res)
who = yield from conn.whoami()
print(who)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(do())
loop.run_until_complete(do())
And console output with enabled PYTHONASYNCIODEBUG
env var:
➔ PYTHONASYNCIODEBUG=1 python test_bonsai.py
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.108 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.112 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.113 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.103 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.101 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.106 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.110 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.111 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.105 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.102 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.107 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.104 seconds
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:298> took 0.293 seconds
[]
dn:cn=secret
Executing <Handle cancelled AIOLDAPConnection._ready(7, <Future pendi...nection.py:31>) created at /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py:262> took 0.263 seconds
[]
dn:cn=secret
Sometimes first connection doesn't work at all...
Same code on Linux works well:
root@e2c92ff9d4f6:/tmp# PYTHONASYNCIODEBUG=1 python test_bonsai.py
Executing <Handle cancelled AIOLDAPConnection._ready(9, <Future pendi...nection.py:31>) created at /usr/local/lib/python3.6/asyncio/selector_events.py:298> took 0.437 seconds
[]
dn:cn=secret
Executing <Handle cancelled AIOLDAPConnection._ready(9, <Future pendi...nection.py:31>) created at /usr/local/lib/python3.6/asyncio/selector_events.py:262> took 0.368 seconds
[]
dn:cn=secret
root@e2c92ff9d4f6:/tmp#
Any ideas?
Hi,
Just tried to use library and got exception on import:
In [1]: import bonsai
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-1-52837d26041c> in <module>()
----> 1 import bonsai
/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/__init__.py in <module>()
1 from .ldapdn import LDAPDN
2 from .ldapurl import LDAPURL
----> 3 from .ldapconnection import LDAPConnection
4 from .ldapconnection import LDAPSearchScope
5 from .ldapentry import LDAPEntry
/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/ldapconnection.py in <module>()
2 from typing import Union, Any, List, Iterator, Tuple
3
----> 4 from ._bonsai import ldapconnection
5 from .ldapdn import LDAPDN
6 from .ldapentry import LDAPEntry
ImportError: dlopen(/Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so, 2): Symbol not found: _ldap_create_passwordpolicy_control
Referenced from: /Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so
Expected in: flat namespace
in /Users/ng/.virtualenvs/env/lib/python3.6/site-packages/bonsai/_bonsai.cpython-36m-darwin.so
In [2]:
I use latest version of bonsai
from PyPI and Python 3.6.
Any ideas?
Testing on Fedora30 with module built against openldap-devel-2.4.47-1.fc30.x86_64
Code to reproduce:
import asyncio
import bonsai
bonsai.set_connect_async(True)
bonsai.set_debug(True)
LDAP_URL = "ldaps://ldaps.capable.lan"
CACERT_FILE = "ca.crt"
async def main():
print("running")
cli = bonsai.LDAPClient(LDAP_URL)
cli.set_ca_cert(CACERT_FILE)
async with cli.connect(is_async=True, timeout=5):
pass
asyncio.run(main())
If I change bonsai.set_connect_async(True)
to False
the connection succeeds but the code blocks. Which causes problems in the app I am building when an ldap server becomes unreachable.
Changing the URL to ldap://
with bonsai.set_connect_async(True)
works as expected.
The truncated debug output from running the example code is included below
Thanks!
DBG: ldapconnection_new [self:0x7f485f110648]
DBG: ldapconnection_init (self:0x7f485f110648)
DBG: ldapconnection_open (self:0x7f485f110648)
DBG: connecting (self:0x7f485f110648)
DBG: create_conn_info (mech:SIMPLE, sock:6, creds:0x7f486ce6e9f0)
DBG: ldapconnectiter_new [self:0x7f485ed4ed68]
DBG: create_init_thread_data (client:0x7f485ed449e8, sock:6)
DBG: create_init_thread (ld:0x559352f0bf40, info:0x5593530b5b00, thread:0)
DBG: ldap_init_thread_func (params:0x559352f0bf40)
DBG: set connecting async: 1
DBG: ldap_init_thread_func [retval:0]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:1, thread:139948802778880, timeout:-1, misc:0x559352f0bf40)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x7f485ed4ed68)
DBG: binding [state:3]
DBG: _ldap_bind (ld:0x7f4858000b20, info:0x5593530b5b00, ppolicy:0, result:(nil), msgid:0)
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]
DBG: binding [state:4]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_fileno (self:0x7f485f110648)[desc:8]
DBG: ldapconnection_result (self:0x7f485f110648, args:0x7f485f8eafd0, kwds:(nil))[msgid:7]
DBG: LDAPConnection_Result (self:0x7f485f110648, msgid:7, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x7f485ed4ed68, timeout:-1) [tls:0, state:4]
... The binding to LDAPConnectIter_Next block repeats until the timeout is reached
Traceback (most recent call last):
File "./bonsai_ldaps_hang.py", line 21, in <module>
asyncio.run(main())
File "/usr/lib64/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/usr/lib64/python3.7/asyncio/base_events.py", line 584, in run_until_complete
return future.result()
File "./bonsai_ldaps_hang.py", line 18, in main
async with cli.connect(is_async=True, timeout=5):
File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 30, in __aenter__
return await self.__open_coro
File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 63, in _poll
raise exc
File "bonsai/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 59, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/lib64/python3.7/asyncio/tasks.py", line 423, in wait_for
raise futures.TimeoutError()
concurrent.futures._base.TimeoutError
When I try to run the paged search with the client connection as asynchronous I get all the results I'm expecting (1238)
def paged_search(*args, **kwargs):
global client
if not client:
client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
client.set_server_chase_referrals(False)
client.set_auto_page_acquire(True)
client.set_credentials("SIMPLE", user=settings['ldap']['user'],
password=settings['ldap']['password'])
final_result=[]
logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
with client.connect(is_async=False, timeout=settings['ldap']['timeout']) as conn:
result = conn.paged_search(*args, **kwargs)
for r in result:
final_result.append(r)
logger.debug(len(final_result))
return final_result
# Output
# DEBUG:aiohttp.server:1238
However when I change the flags and functions to asynchronous I'm only getting the first page of results
async def paged_search(*args, **kwargs):
global client
if not client:
client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
client.set_server_chase_referrals(False)
client.set_auto_page_acquire(True)
client.set_credentials("SIMPLE", user=settings['ldap']['user'],
password=settings['ldap']['password'])
final_result=[]
logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
async with client.connect(is_async=True, timeout=settings['ldap']['timeout']) as conn:
result = await conn.paged_search(*args, **kwargs)
for r in result:
final_result.append(r)
logger.debug(len(final_result))
return final_result
# OUTPUT
# DEBUG:aiohttp.server:1003
I suspect that it's only returning the first page of results and what I've also noticed is that I'm also getting an extra 3 empty entries which I haven't been able to track down as to why yet.
I did one last test based on a previous issue you resolved #31 where you turned off auto_page_acquire and used the function acquire_next_page and that seemed to work for me
async def paged_search(*args, **kwargs):
global client
if not client:
client = bonsai.LDAPClient(f"ldap://{settings['ldap']['host']}")
client.set_server_chase_referrals(False)
client.set_auto_page_acquire(False)
client.set_credentials("SIMPLE", user=settings['ldap']['user'],
password=settings['ldap']['password'])
final_result=[]
logger.info(f"Connecting to ldap://{settings['ldap']['host']}...")
async with client.connect(is_async=True, timeout=settings['ldap']['timeout']) as conn:
result = await conn.paged_search(*args, **kwargs)
for r in result:
final_result.append(r)
logger.debug(len(final_result))
msgid = result.acquire_next_page()
while msgid is not None:
result = await conn._evaluate(msgid)
for r in result:
final_result.append(r)
msgid = result.acquire_next_page()
logger.debug(len(final_result))
return final_result
# Output
# DEBUG:aiohttp.server:1003
# DEBUG:aiohttp.server:1238
This was tested using bonsai 1.2.1 running in a docker container based on the image python:3.7.6-buster
Hi again
Sorry for bothering. I am looking for a way to retrieve also operational attributes from a search
. I specifically need for a way to obtain the values for memberOf
associated to an entry that belongs to some groupOfNames
.
Is there any way to do that easily?
Thanks again for this nice library!
Hi,
Thanks for this module. On RedHat 6, I find that I need to use the -std=c99 compiler option otherwise I see this error doing when doing a 'pip install pyldap':
building '_ldap' extension gcc -pthread -Werror=declaration-after-statement -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -DHAVE_SASL -DHAVE_TLS -DHAVE_LIBLDAP_R -DHAVE_LIBLDAP_R -DLDAPMODULE_VERSION=2.4.20 -IModules -I/usr/include -I/usr/include/sasl -I/usr/local/include -I/usr/local/include/sasl -I/home/jspeno200/virtualenvs/dashboard/include -I/usr/local/include/python3.4m -c Modules/LDAPObject.c -o build/temp.linux-x86_64-3.4/Modules/LDAPObject.o In file included from Modules/LDAPObject.c:18: /usr/include/sasl/sasl.h:349: warning: function declaration isn’t a prototype Modules/LDAPObject.c: In function ‘l_ldap_result4’: Modules/LDAPObject.c:1023: error: ISO C90 forbids mixed declarations and code error: command 'gcc' failed with exit status 1
Is there something we can do to make installing work without needing the extra compiler flags? Please let me know how I can help.
Hi there,
Quick question, when I do async connect is it thread-pooled or is it going truly async?
async with client.connect(is_async=True) as conn:
res = await conn.search("ou=nerdherd,dc=bonsai,dc=test", 2)
print(res)
who = await conn.whoami()
print(who)
Thanks,
Roman
Hi!
Code:
import asyncio
import pycurl # code work whitout this import
from bonsai import LDAPClient, LDAPSearchScope
async def do():
LDAP_SERVER = ''
LDAP_USER = ''
LDAP_PASSWORD = ''
client = LDAPClient(LDAP_SERVER)
client.set_credentials("SIMPLE", (LDAP_USER, LDAP_PASSWORD))
async with client.connect(is_async=True) as conn:
await conn.search(
base="cn=users_catalog,ou=users,dc=<DC>",
scope=LDAPSearchScope.SUBTREE,
)
loop = asyncio.get_event_loop()
loop.run_until_complete(do())
Output:
Assertion failed: (LDAP_VALID( ld )), function ldap_set_option, file options.c, line 456.
Platform:
$ python -V
Python 3.6.3
$ uname -a
Darwin MacBook-Pro-admin.local 17.0.0 Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64 x86_64
$ clang -v
Apple LLVM version 9.0.0 (clang-900.0.38)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Darwin yangEverdeMac-mini.local 20.6.0 Darwin Kernel Version 20.6.0: Wed Jun 23 00:26:27 PDT 2021; root:xnu-7195.141.2~5/RELEASE_ARM64_T8101 arm64
Python 3.8.11
bonsai==1.2.1
brew list openldap
/opt/homebrew/Cellar/openldap/2.5.6/.bottle/etc/ (37 files)
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapadd
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapcompare
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapdelete
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapexop
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapmodify
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapmodrdn
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldappasswd
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapsearch
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapurl
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapvc
/opt/homebrew/Cellar/openldap/2.5.6/bin/ldapwhoami
/opt/homebrew/Cellar/openldap/2.5.6/include/ (10 files)
/opt/homebrew/Cellar/openldap/2.5.6/lib/liblber-2.5.0.dylib
/opt/homebrew/Cellar/openldap/2.5.6/lib/libldap-2.5.0.dylib
/opt/homebrew/Cellar/openldap/2.5.6/lib/pkgconfig/ (2 files)
/opt/homebrew/Cellar/openldap/2.5.6/lib/ (4 other files)
/opt/homebrew/Cellar/openldap/2.5.6/libexec/slapd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapacl
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapadd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapauth
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapcat
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapdn
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapindex
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapmodify
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slappasswd
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slapschema
/opt/homebrew/Cellar/openldap/2.5.6/sbin/slaptest
/opt/homebrew/Cellar/openldap/2.5.6/share/man/ (257 files)
import bonsai
Traceback (most recent call last):
File "", line 1, in
File "/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/init.py", line 3, in
from .ldapconnection import LDAPConnection
File "/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/ldapconnection.py", line 5, in
from ._bonsai import ldapconnection, ldapsearchiter
ImportError: dlopen(/Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so, 2): Symbol not found: _ldap_create_passwordpolicy_control
Referenced from: /Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so
Expected in: flat namespace
in /Users/yangever/PycharmProjects/lpcmdb/venv/lib/python3.8/site-packages/bonsai/_bonsai.cpython-38-darwin.so
pip install ~/Downloads/bonsai-1.2.1-cp38-cp38-macosx_10_14_x86_64.whl
ERROR: bonsai-1.2.1-cp38-cp38-macosx_10_14_x86_64.whl is not a supported wheel on this platform.
maybe bonsai not support arm64? but i'am install the openldap, it shoud has lib/so ..etc requirements..
I don't think persistent search is implemented in this library, nor a generic way to expose calling an arbitrary extended operation from python - would you be open to a pull request for those features?
If so, are there any requirements you have before you'd merge that you can think of off the top of your head, or should I take my best guess at the API for each feature, issue the request and just work from there?
I am trying to get members of a group and find their email address. The AD group contains 50k+ results. When I query the AD group I can only get 1500 results. I tried the acquire_next_page flag but couldn't get the next set of attributes.
Hi,
The "timeout" parameter on LDAPClient.connect() seems to only affect the LDAP bind process, and not the network connection. So if the network connection times out (aka. SYN timeout) Bonsai seems to hang forever.
Example code:
import bonsai
def main():
cli = bonsai.LDAPClient("ldap://bonsai.test.watn.no")
print("Connecting...")
cli.connect(timeout=2.0)
print("Connected!")
cli.close()
if __name__ == "__main__":
main()
(bonsai.test.watn.no drops packets on port 389, so the connection will end in SYN timeout).
This also affect LDAPS connections if the SSL handshake times out. I'm testing on Linux, Fedora 27, with OpenLDAP 2.4.45-4.fc27.
(otherwise; great module! Thanks for making it.)
Hi!
I am now trying to use modify_password
in async mode, which works fine when no new password is provided.
Unfortunately, this raises a strange error in async mode:
Traceback (most recent call last):
File "/home/lab/bonsai/update_me.py", line 60, in <module>
asyncio.run(asynchronous(oldpass, newpass))
File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/home/lab/bonsai/update_me.py", line 42, in asynchronous
res = await conn.modify_password(
File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 60, in _poll
raise exc
File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 55, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/lib/python3.9/asyncio/tasks.py", line 442, in wait_for
return await fut
File "/home/lab/bonsai/venv/lib/python3.9/site-packages/bonsai/asyncio/aioconnection.py", line 40, in _ready
res = super().get_result(msg_id)
bonsai.errors.InvalidMessageID: Given message ID is invalid or the associated operation is already finished. (0xFF9C [-100])
When a new password is provided, the server is expected to return no data, which is handled here:
bonsai/src/_bonsai/ldapconnection.c
Line 1080 in 034ec67
In this situation, None
is returned as the result to the python caller.
In async mode, a result None
means that the caller has to try again later as described here:
This can be fixed by returning True
or any other value instead of None
, but that would break the user API and existing applications.
On the other hand, rewriting the way these loops work may be a lot of work :-(.
Regards,
Romain
Hi, thanks for this nice library!
I'd like to use it with my project, but as far as I could get, there is no clear way to use STARTTLS
mechanism. When creating an LDAPClient
instance there is just a boolean tls
parameter. Trying to connect to an OpenLDAP server which advertises itself on port 389
and requires confientiality, I get
LDAPError: Confidentiality required. confidentiality required (0x000D [13])
with a connection with tls=False
. Trying to connecting with a connection with tls=True
I get
ConnectionError: Can't contact LDAP server. (0xFFFF [-1])
I think because it tries to wrap the connection in a tls socket.
The only reference I found in this repo to STARTTLS
is this commit (f91d203), but I don't get its meaning.
Can you help me understand it? Thanks!
Is there some method in bonsai to escape characters when building filter or dn for search?
call: await client.connect(is_async=True)
on a server that is not reachable
Traceback (most recent call last):
File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 60, in _poll
return await asyncio.wait_for(fut, timeout)
File "/usr/lib/python3.7/asyncio/tasks.py", line 388, in wait_for
return await fut
File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 45, in _ready
res = super().get_result(msg_id)
bonsai.errors.ConnectionError: Can't contact LDAP server. (0xFFFF [-1])
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/web_util.py", line 41, in rest_request_handler_inner
result = await func(request)
File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/web_app.py", line 92, in get_branches
return await model.get_branches()
File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/model/__init__.py", line 232, in get_branches
async with self.connection_manager as connection:
File "/home/garyvdm/dev/fnb/bpm-ng/python/bpm/model/__init__.py", line 75, in __aenter__
self.connection = await self.client.connect(is_async=True)
File "/home/garyvdm/dev/fnb/bpm-ng/ve/lib/python3.7/site-packages/bonsai/asyncio/aioconnection.py", line 62, in _poll
self._loop.remove_reader(self.fileno())
File "uvloop/loop.pyx", line 2302, in uvloop.loop.Loop.remove_reader
File "uvloop/loop.pyx", line 724, in uvloop.loop.Loop._remove_reader
File "uvloop/loop.pyx", line 682, in uvloop.loop.Loop._fileobj_to_fd
ValueError: Invalid file descriptor: -1
As you can see, a bonsai.errors.ConnectionError
but another error was raised in a handler of that exception.
A bonsai.errors.ConnectionError
is raised.
i am trying to connect to a ldap server that uses a certificated that is singed by a internal ca.
i tried to set the path to the CA file using "set_ca_cert_dir" like this.
client.set_ca_cert_dir('/path/to/cert/contrib/ca-bundle.pem')
or
client.set_ca_cert_dir('/path/to/cert/contrib/')
but the certificate is not picked up. any clue what needs to be done to get this working?
Hi
This library looks pretty neat, but when i installed it and tried to import it on OS X i received the following import error:
Python 3.4.3 (default, Jul 13 2015, 12:18:23)
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyldap
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/__init__.py", line 3, in <module>
from pyldap.ldapconnection import LDAPConnection
File "/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/ldapconnection.py", line 1, in <module>
from pyldap._cpyldap import _LDAPConnection
ImportError: dlopen(/Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so, 2): Symbol not found: _ldap_control_find
Referenced from: /Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so
Expected in: flat namespace
in /Users/kradalby/git/garru/env/lib/python3.4/site-packages/pyldap/_cpyldap.so
I guess it may be a compile error?
OS X version is 10.10.4
Python 3.4.3
I have not installed any additional LDAP libs, or anything, this may be the problem?
Install command:
pip install git+https://github.com/Noirello/PyLDAP.git
I will be happy to provide any additional information if needed.
Hi,
If we are talking about asyncio and Python 3.5+ we want to use async/await syntax instead of coroutine decorator and yield from
.
Here an example how I see it:
import asyncio
import bonsai
async def do():
client = bonsai.LDAPClient('...')
client.set_credentials('SIMPLE', ('...', '...'))
client.set_cert_policy('allow')
async with client.connect(is_async=True, timeout=10.0) as conn:
res = await conn.search('...', 2)
print(res)
who = await conn.whoami()
print(who)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(do())
loop.run_until_complete(do())
Is it possible?
Sorry to create an issue just to ask this bit, has development stopped?
Hi!
While trying to use bonsai
to change a password on a OpenLDAP server I encountered a weird failure.
From a python point of view I get the following backtrace:
(venv) $ python update_password.py
Traceback (most recent call last):
File "/home/lab/update_password.py", line 18, in <module>
res = conn.modify_password(
File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 394, in modify_password
return super().modify_password(user, new_password, old_password, timeout)
File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 64, in modify_password
return self._evaluate(
File "/home/lab/venv/lib/python3.9/site-packages/bonsai/ldapconnection.py", line 246, in _evaluate
return self.get_result(msg_id, timeout)
bonsai.errors.LDAPError: password hash failed. (0x0050 [80])
This test program close to no interrest here as many tests were performed to pinpoint the origin of this issue.
On the server side, nothing seems unusual except for these few lines in the (verbose) log:
Nov 1 22:21:44 ldap slapd[8174]: send_ldap_extended: err=80 oid= len=0
Nov 1 22:21:44 ldap slapd[8174]: send_ldap_response: msgid=2 tag=120 err=80
Nov 1 22:21:44 ldap slapd[8174]: conn=1015 op=1 RESULT oid= err=80 text=password hash failed
The server runs on Ubuntu 18.04.6 LTS with OpenLDAP 2.4.45+dfsg-1ubuntu1.10
.
It uses and requires SSL, and the password strategy is so that it is stored hashed using sha512crypt.
I can already update passwords when using pam from another connected server on the network.
I can also successfully get a new password when new_password
is not provided to modify_password
,
in which case the new password is gathered from the server response.
After more digging it seems to come from the following line:
bonsai/src/_bonsai/ldapconnection.c
Line 744 in 034ec67
ber_printf(ber, "n}");
When replaced by the following, everything now works as expected:
ber_printf(ber, "}");
There were already some discussions on samba's bugtracker about this:
https://bugzilla.samba.org/show_bug.cgi?id=5886
This attachment caught my interest (fix for smbpasswd to work with OpenLDAP 2.4):
https://bugzilla.samba.org/attachment.cgi?id=5461
Their current implementation even removed the N
:
https://github.com/samba-team/samba/blob/master/source3/passdb/pdb_ldap.c#L1762
Here is the same line for pam_ldap
:
https://github.com/PADL/pam_ldap/blob/master/pam_ldap.c#L3267
And here is the line in OpenLDAP implementation:
https://git.openldap.org/openldap/openldap/-/blob/master/clients/tools/ldappasswd.c#L299
It seems like N
does not work on windows as stated in commit 32f66de.
With OpenLDAP headers, this seems to be a debug modifier (ignored in production):
https://git.openldap.org/openldap/openldap/-/blob/master/libraries/liblber/encode.c#L553
Should this fix be working on windows, I suggest to use the fix mentioned earlier!
I will open a merge request attached to this issue.
Thanks!
Romain
LDAPConnection needs to implement __aenter__
and __aexit__
methods to be able to use async with
(as Issue #12 pointed it out).
I recently got Macbook system refreshed and I am trying to get bonsai working -
openssl 1.1.1f
openldap 2.4.49
Python 3.7.7
Bonsai 1.2.0
import asyncio
import bonsai
CACERT_FILE = ".//cacerts.pem"
bonsai.set_connect_async(False)
bonsai.set_debug(True, -1)
server_name ="ldaps://server.com"
client = bonsai.LDAPClient(server_name)
client.set_ca_cert(CACERT_FILE)
client.set_credentials("SIMPLE", user=user, password=password)
client.set_auto_page_acquire(True)
base = "x,y,z"
async def get_last_login(search_filter):
async with client.connect(is_async=True) as conn:
res = await conn.search(base=base, scope=2, filter_exp=search_filter, attrlist=['*'])
print(res[0])
if __name__ == '__main__':
search_filter = "(&(objectClass=group)(samaccountname={}))".format('Test')
print(search_filter)
loop = asyncio.get_event_loop()
loop.run_until_complete(get_last_login(search_filter))
ldap_url_parse_ext(ldap://localhost/)
ldap_init: trying /usr/local/etc/openldap/ldap.conf
ldap_init: using /usr/local/etc/openldap/ldap.conf
ldap_init: HOME env is /Users/MYUSER
ldap_init: trying /Users/MYUSER/ldaprc
ldap_init: trying /Users/MYUSER/.ldaprc
ldap_init: trying ldaprc
ldap_init: LDAPCONF env is NULL
ldap_init: LDAPRC env is NULL
(&(objectClass=group)(samaccountname=Test))
DBG: ldapconnection_new [self:0x10a2a8fa0]
DBG: ldapconnection_init (self:0x10a2a8fa0)
DBG: ldapconnection_open (self:0x10a2a8fa0)
DBG: connecting (self:0x10a2a8fa0)
DBG: create_conn_info (mech:SIMPLE, sock:7, creds:0x10a2c2780)
DBG: ldapconnectiter_new [self:0x10a2cf4e0]
DBG: create_init_thread_data (client:0x1063bba50, sock:7)
DBG: create_init_thread (ld:0x7ff42fc02400, info:0x7ff42fc1f290, thread:0)
DBG: ldap_init_thread_func (params:0x7ff42fc02400)
ldap_create
ldap_url_parse_ext(ldaps://server.com:636)
DBG: set connecting async: 0
DBG: ldap_init_thread_func [retval:0]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_fileno (self:0x10a2a8fa0)[desc:0, dummy]
DBG: ldapconnection_result (self:0x10a2a8fa0, args:0x109e19f10, kwds:0x0)[msgid:8]
DBG: LDAPConnection_Result (self:0x10a2a8fa0, msgid:8, millisec:-1)
DBG: LDAPConnectIter_Next (self:0x10a2cf4e0, timeout:-1) [tls:0, state:0]
DBG: _ldap_finish_init_thread (async:1, thread:123145450450944, timeout:-1, misc:0x7ff42fc02400)
DBG: _pthread_mutex_timedlock
DBG: set_certificates (self:0x10a2cf4e0)Process finished with exit code 11
I am not sure if it is version compatibility or something else, is there something that I need to change?
When using bonsai with python 3.7 I get this warning:
.../lib/python3.7/site-packages/bonsai/ldapurl.py:44: FutureWarning: Possible nested set at position 52
Hi. I'm not sure that it is but it seems so. So it raise exception about object class violation ("cn" needed) when I try to rename entry.
Could you please explain how to:
is there a connection pool in place, or do i have to build my own connection pool on top of this library?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.