1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
|
"""
ldapobject.py - wraps class _ldap.LDAPObject
written by Michael Stroeder <michael@stroeder.com>
\$Id: ldapobject.py,v 1.18 2002/02/02 11:47:00 stroeder Exp $
License:
Public domain. Do anything you want with this module.
Compability:
- Tested with Python 2.0+ but should work with Python 1.5.x
- LDAPObject class should be exactly the same like _ldap.LDAPObject
Usage:
Directly imported by ldap/__init__.py. The symbols of _ldap are
overridden.
Thread-lock:
Basically calls into the LDAP lib are serialized by the module-wide
lock ldap._ldap_lock. To avoid long-time blocking of other threads
synchronous methods like search_s() etc. and the result() method
were rewritten to do solely asynchronous LDAP lib calls with zero
timeout.
The timeout handling is done within the method result() which probably leads
to less exact timing.
"""
__version__ = '0.0.6'
__all__ = ['LDAPObject']
if __debug__:
# Tracing is only supported in debugging mode
import traceback
import sys,time,_ldap,ldap
class LDAPObject:
"""
Drop-in wrapper class around _ldap.LDAPObject
"""
def __init__(self,uri,trace_level=0,trace_file=sys.stdout):
self._trace_level = trace_level
self._trace_file = trace_file
self._l = self._ldap_call(_ldap.initialize,uri)
def _ldap_call(self,func,*args,**kwargs):
"""Wrapper method mainly for trace logs"""
if __debug__:
if self._trace_level>=1 and func.__name__!='result':
self._trace_file.write('*** %s.%s (%s,%s)\n' % (
self.__module__,
self.__class__.__name__+'.'+func.__name__,
repr(args),repr(kwargs)
))
if self._trace_level>=2:
traceback.print_stack(file=self._trace_file)
result = ldap._ldap_call(func,*args,**kwargs)
if __debug__:
if self._trace_level>=1 and result!=None and result!=(None,None):
self._trace_file.write('=> result: %s\n' % (repr(result)))
return result
def __setattr__(self,name,value):
if not name in ['_l','_trace_level','_trace_file']:
if __debug__:
if self._trace_level>=1:
self._trace_file.write('*** %s:' % (self.__module__),\
self.__class__.__name__+'.__setattr__(%s,%s)' % (name,value)
)
if self._trace_level>=2:
traceback.print_stack(file=self._trace_file)
ldap._ldap_lock.acquire()
try:
setattr(self._l,name,value)
finally:
ldap._ldap_lock.release()
else:
self.__dict__[name] = value
def __getattr__(self,name):
if not name in ['_l','_trace_level','_trace_file']:
ldap._ldap_lock.acquire()
try:
value = getattr(self._l,name)
finally:
ldap._ldap_lock.release()
if __debug__:
if self._trace_level>=1:
self._trace_file.write('*** %s:' % (self.__module__),\
self.__class__.__name__+'.__getattr__(%s)' % (name),'=>',value
)
if self._trace_level>=2:
traceback.print_stack(file=sys.stdout)
else:
value = self.__dict__[name]
return value
def abandon(self,msgid):
"""
abandon(msgid) -> None
Abandons or cancels an LDAP operation in progress. The msgid should
be the message id of an outstanding LDAP operation as returned
by the asynchronous methods search(), modify() etc. The caller
can expect that the result of an abandoned operation will not be
returned from a future call to result().
"""
return self._ldap_call(self._l.abandon,msgid)
def add(self,dn,modlist):
"""
add(dn, modlist) -> int
This function is similar to modify(), except that no operation
integer need be included in the tuples.
"""
return self._ldap_call(self._l.add,dn,modlist)
def add_s(self,dn,modlist):
msgid = self.add(dn,modlist)
self.result(msgid)
def bind(self,who,cred,method):
"""
bind(who, cred, method) -> int
"""
return self._ldap_call(self._l.bind,who,cred,method)
def bind_s(self,who,cred,method):
"""
bind_s(who, cred, method) -> None
"""
msgid = self.bind(who,cred,method)
self.result(msgid)
def compare(self,*args,**kwargs):
"""
compare(dn, attr, value) -> int
compare_s(dn, attr, value) -> int
Perform an LDAP comparison between the attribute named attr of
entry dn, and the value value. The synchronous form returns 0
for false, or 1 for true. The asynchronous form returns the
message id of the initiates request, and the result of the
asynchronous compare can be obtained using result().
Note that this latter technique yields the answer by raising
the exception objects COMPARE_TRUE or COMPARE_FALSE.
A design bug in the library prevents value from containing
nul characters.
"""
return self._ldap_call(self._l.compare,*args,**kwargs)
def compare_s(self,*args,**kwargs):
msgid = self.compare(*args,**kwargs)
try:
self.result(msgid)
except _ldap.COMPARE_TRUE:
return 1
except _ldap.COMPARE_FALSE:
return 0
return None
def delete(self,dn):
"""
delete(dn) -> int
delete_s(dn) -> None
Performs an LDAP delete operation on dn. The asynchronous
form returns the message id of the initiated request, and the
result can be obtained from a subsequent call to result().
"""
return self._ldap_call(self._l.delete,dn)
def delete_s(self,dn):
msgid = self.delete(dn)
self.result(msgid)
def destroy_cache(self):
"""
destroy_cache() -> None
Turns off caching and removed it from memory.
"""
self._ldap_call(self._l.destroy_cache,)
def disable_cache(self):
"""
disable_cache() -> None
Temporarily disables use of the cache. New requests are
not cached, and the cache is not checked when returning
results. Cache contents are not deleted.
"""
self._ldap_call(self._l.disable_cache,)
def enable_cache(self,timeout=_ldap.NO_LIMIT,maxmem=_ldap.NO_LIMIT):
"""
enable_cache([timeout=NO_LIMIT, [maxmem=NO_LIMIT]]) -> None
Using a cache often greatly improves performance. By default
the cache is disabled. Specifying timeout in seconds is used
to decide how long to keep cached requests. The maxmem value
is in bytes, and is used to set an upper bound on how much
memory the cache will use. A value of NO_LIMIT for either
indicates unlimited. Subsequent calls to enable_cache()
can be used to adjust these parameters.
This and other caching methods are not available if the library
and the ldap module were compiled with support for it.
"""
self._ldap_call(self._l.enable_cache,timeout,maxmem)
def fileno(self):
"""
fileno() -> int
Return the file descriptor associated with this connection.
"""
return self._ldap_call(self._l.fileno,)
def flush_cache(self):
"""
flush_cache() -> None
Deletes the cache's contents, but does not affect it in any other way.
"""
self._ldap_call(self._l.flush_cache)
def manage_dsa_it(self,enable,critical=0):
"""
manage_dsa_it() -> None
Enable or disable manageDSAit mode (see draft-zeilenga-ldap-namedref)
"""
self._ldap_call(self._l.manage_dsa_it,enable,critical)
def modify(self,dn,modlist):
"""
modify(dn, modlist) -> int
modify_s(dn, modlist) -> None
Performs an LDAP modify operation on an entry's attributes.
dn is the DN of the entry to modify, and modlist is the list
of modifications to make to the entry.
Each element of the list modlist should be a tuple of the form
(mod_op,mod_type,mod_vals), where mod_op is the operation (one
of MOD_ADD, MOD_DELETE, or MOD_REPLACE), mod_type is a string
indicating the attribute type name, and mod_vals is either
a string value or a list of string values to add, delete or
replace respectively. For the delete operation, mod_vals may
be None indicating that all attributes are to be deleted.
The asynchronous modify() returns the message id of the
initiated request.
"""
return self._ldap_call(self._l.modify,dn,modlist)
def modify_s(self,dn,modlist):
msgid = self.modify(dn,modlist)
self.result(msgid)
def modrdn(self,dn,newrdn,delold=1):
"""
modrdn(dn, newrdn [,delold=1]) -> int
modrdn_s(dn, newrdn [,delold=1]) -> None
Perform a modify RDN operation. These routines take dn, the
DN of the entry whose RDN is to be changed, and newrdn, the
new RDN to give to the entry. The optional parameter delold
is used to specify whether the old RDN should be kept as
an attribute of the entry or not. The asynchronous version
returns the initiated message id.
This operation is emulated by rename() and rename_s() methods
since the modrdn2* routines in the C library are deprecated.
"""
return self.rename(dn,newrdn,None,delold)
def modrdn_s(self,dn,newrdn,delold=1):
self.rename_s(dn,newrdn,None,delold)
def rename(self,dn,newrdn,newsuperior=None,delold=1):
"""
rename(dn, newrdn [, newsuperior=None] [,delold=1]) -> int
rename_s(dn, newrdn [, newsuperior=None] [,delold=1]) -> None
Perform a rename entry operation. These routines take dn, the
DN of the entry whose RDN is to be changed, newrdn, the
new RDN, and newsuperior, the new parent DN, to give to the entry.
If newsuperior is None then only the RDN is modified.
The optional parameter delold is used to specify whether the
old RDN should be kept as an attribute of the entry or not.
The asynchronous version returns the initiated message id.
This actually corresponds to the rename* routines in the
LDAP-EXT C API library.
"""
return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold)
def rename_s(self,dn,newrdn,newsuperior=None,delold=1):
msgid = self.rename(dn,newrdn,newsuperior,delold)
self.result(msgid)
def result(self,msgid=_ldap.RES_ANY,all=1,timeout=-1):
"""
result([msgid=RES_ANY [,all=1 [,timeout=-1]]]) -> (result_type, result_data)
This method is used to wait for and return the result of an
operation previously initiated by one of the LDAP asynchronous
operation routines (eg search(), modify(), etc.) They all
returned an invocation identifier (a message id) upon successful
initiation of their operation. This id is guaranteed to be
unique across an LDAP session, and can be used to request the
result of a specific operation via the msgid parameter of the
result() method.
If the result of a specific operation is required, msgid should
be set to the invocation message id returned when the operation
was initiated; otherwise RES_ANY should be supplied.
The all parameter only has meaning for search() responses
and is used to select whether a single entry of the search
response should be returned, or to wait for all the results
of the search before returning.
A search response is made up of zero or more search entries
followed by a search result. If all is 0, search entries will
be returned one at a time as they come in, via separate calls
to result(). If all is 1, the search response will be returned
in its entirety, i.e. after all entries and the final search
result have been received.
The method returns a tuple of the form (result_type,
result_data). The result_type is a string, being one of:
'RES_BIND', 'RES_SEARCH_ENTRY', 'RES_SEARCH_RESULT',
'RES_MODIFY', 'RES_ADD', 'RES_DELETE', 'RES_MODRDN', or
'RES_COMPARE'.
The constants RES_* are set to these strings, for convenience.
See search() for a description of the search result's
result_data, otherwise the result_data is normally meaningless.
The result() method will block for timeout seconds, or
indefinitely if timeout is negative. A timeout of 0 will effect
a poll. The timeout can be expressed as a floating-point value.
If a timeout occurs, a TIMEOUT exception is raised, unless
polling (timeout = 0), in which case (None, None) is returned.
"""
ldap_result = self._ldap_call(self._l.result,msgid,0,0)
if not all:
return ldap_result
start_time = time.time()
all_results = []
while all:
while ldap_result[0] is None:
if (timeout>=0) and (time.time()-start_time>timeout):
self._ldap_call(self._l.abandon,msgid)
raise _ldap.TIMELIMIT_EXCEEDED(
"LDAP time limit (%d secs) exceeded." % (timeout)
)
ldap_result = self._ldap_call(self._l.result,msgid,0,0)
if ldap_result[1] is None:
break
all_results.extend(ldap_result[1])
ldap_result = None,None
return all_results
def search(self,base,scope,filterstr,attrlist=None,attrsonly=0):
"""
search(base, scope, filter [,attrlist=None [,attrsonly=0]]) -> int
search_s(base, scope, filter [,attrlist=None [,attrsonly=0]])
search_st(base, scope, filter [,attrlist=None [,attrsonly=0 [,timeout=-1]]])
Perform an LDAP search operation, with base as the DN of
the entry at which to start the search, scope being one of
SCOPE_BASE (to search the object itself), SCOPE_ONELEVEL
(to search the object's immediate children), or SCOPE_SUBTREE
(to search the object and all its descendants).
filter is a string representation of the filter to
apply in the search (see RFC 2254).
When using the asynchronous form and result(), the all parameter
affects how results come in. For all set to 0, result tuples
trickle in (with the same message id), and with the result type
RES_SEARCH_ENTRY, until the final result which has a result
type of RES_SEARCH_RESULT and a (usually) empty data field.
When all is set to 1, only one result is returned, with a
result type of RES_SEARCH_RESULT, and all the result tuples
listed in the data field.
Each result tuple is of the form (dn,entry), where dn is a
string containing the DN (distinguished name) of the entry, and
entry is a dictionary containing the attributes.
Attributes types are used as string dictionary keys and attribute
values are stored in a list as dictionary value.
The DN in dn is extracted using the underlying ldap_get_dn(),
which may raise an exception of the DN is malformed.
If attrsonly is non-zero, the values of attrs will be
meaningless (they are not transmitted in the result).
The retrieved attributes can be limited with the attrlist
parameter. If attrlist is None, all the attributes of each
entry are returned.
The synchronous form with timeout, search_st(), will block
for at most timeout seconds (or indefinitely if timeout is
negative). A TIMEOUT exception is raised if no result is
received within the time.
"""
return self._ldap_call(self._l.search,base,scope,filterstr,attrlist,attrsonly)
def search_s(self,base,scope,filterstr,attrlist=None,attrsonly=0):
return self.search_st(base,scope,filterstr,attrlist,attrsonly,timeout=-1)
def search_st(self,base,scope,filterstr,attrlist=None,attrsonly=0,timeout=-1):
msgid = self.search(base,scope,filterstr,attrlist,attrsonly)
return self.result(msgid,all=1,timeout=timeout)
def set_cache_options(self,*args,**kwargs):
"""
set_cache_options(option) -> None
Changes the caching behaviour. Currently supported options are
CACHE_OPT_CACHENOERRS, which suppresses caching of requests
that resulted in an error, and
CACHE_OPT_CACHEALLERRS, which enables caching of all requests.
The default behaviour is not to cache requests that result in
errors, except those that result in a SIZELIMIT_EXCEEDED exception.
"""
return self._ldap_call(self._l.set_cache_options,*args,**kwargs)
def set_rebind_proc(self,func):
"""
set_rebind_proc(func) -> None
If a referral is returned from the server, automatic re-binding
can be achieved by providing a function that accepts as an
argument the newly opened LDAP object and returns the tuple
(who, cred, method).
Passing a value of None for func will disable this facility.
Because of restrictions in the implementation, only one
rebinding function is supported at any one time. This method
is only available if the module and library were compiled with
support for it.
"""
self._ldap_call(self._l.set_rebind_proc,func)
def simple_bind(self,who,passwd):
"""
simple_bind(who, passwd) -> int
"""
return self.bind(who,passwd,_ldap.AUTH_SIMPLE)
def simple_bind_s(self,who,passwd):
"""
simple_bind_s(who, passwd) -> None
"""
self.bind_s(who,passwd,_ldap.AUTH_SIMPLE)
def start_tls_s(self,*args,**kwargs):
"""
start_tls_s() -> None
Negotiate TLS with server. The `version' attribute must have been
set to VERSION3 before calling start_tls_s.
If TLS could not be started an exception will be raised.
"""
self._ldap_call(self._l.start_tls_s,*args,**kwargs)
def unbind(self):
"""
unbind_s() -> None
unbind() -> int
This call is used to unbind from the directory, terminate
the current association, and free resources. Once called, the
connection to the LDAP server is closed and the LDAP object
is invalid. Further invocation of methods on the object will
yield an exception.
The unbind and unbind_s methods are identical, and are
synchronous in nature
"""
return self._ldap_call(self._l.unbind)
def unbind_s(self):
msgid = self.unbind()
if msgid!=None:
self.result(msgid)
def uncache_entry(self,dn):
"""
uncache_entry(dn) -> None
Removes all cached entries that make reference to dn. This should be
used, for example, after doing a modify() involving dn.
"""
self._ldap_call(self._l.uncache_entry,dn)
def uncache_request(self,msgid):
"""
uncache_request(msgid) -> None
Remove the request indicated by msgid from the cache.
"""
self._ldap_call(self._l.uncache_request,msgid)
def url_search(self,url,attrsonly=0):
"""
url_search(url [,attrsonly=0])
url_search_s(url [,attrsonly=0])
url_search_st(url [,attrsonly=0 [,timeout=-1]])
These routine works much like search_s*, except that many
search parameters are pulled out of the URL url (see RFC 2255).
"""
return self._ldap_call(self._l.url_search,url,attrsonly)
def url_search_st(self,url,attrsonly=0,timeout=-1):
msgid = self.url_search(url,attrsonly)
search_results = []
for res_type,res_data in self.result(msgid,all=1,timeout=timeout):
search_results.extend(res_data)
return search_results
def url_search_s(self,url,attrsonly=0):
return self.url_search_st(url,attrsonly,timeout=-1)
def get_option(self,option):
return self._ldap_call(self._l.get_option,option)
def set_option(self,option,invalue):
self._ldap_call(self._l.set_option,option,invalue)
|