Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

asyncsearch.py 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. """
  2. ldap.asyncsearch - handle async LDAP search operations
  3. See https://www.python-ldap.org/ for details.
  4. """
  5. import ldap
  6. from ldap import __version__
  7. import ldif
  8. SEARCH_RESULT_TYPES = {
  9. ldap.RES_SEARCH_ENTRY,
  10. ldap.RES_SEARCH_RESULT,
  11. ldap.RES_SEARCH_REFERENCE,
  12. }
  13. ENTRY_RESULT_TYPES = {
  14. ldap.RES_SEARCH_ENTRY,
  15. ldap.RES_SEARCH_RESULT,
  16. }
  17. class WrongResultType(Exception):
  18. def __init__(self,receivedResultType,expectedResultTypes):
  19. self.receivedResultType = receivedResultType
  20. self.expectedResultTypes = expectedResultTypes
  21. Exception.__init__(self)
  22. def __str__(self):
  23. return 'Received wrong result type %s (expected one of %s).' % (
  24. self.receivedResultType,
  25. ', '.join(self.expectedResultTypes),
  26. )
  27. class AsyncSearchHandler:
  28. """
  29. Class for stream-processing LDAP search results
  30. Arguments:
  31. l
  32. LDAPObject instance
  33. """
  34. def __init__(self,l):
  35. self._l = l
  36. self._msgId = None
  37. self._afterFirstResult = 1
  38. def startSearch(
  39. self,
  40. searchRoot,
  41. searchScope,
  42. filterStr,
  43. attrList=None,
  44. attrsOnly=0,
  45. timeout=-1,
  46. sizelimit=0,
  47. serverctrls=None,
  48. clientctrls=None
  49. ):
  50. """
  51. searchRoot
  52. See parameter base of method LDAPObject.search()
  53. searchScope
  54. See parameter scope of method LDAPObject.search()
  55. filterStr
  56. See parameter filter of method LDAPObject.search()
  57. attrList=None
  58. See parameter attrlist of method LDAPObject.search()
  59. attrsOnly
  60. See parameter attrsonly of method LDAPObject.search()
  61. timeout
  62. Maximum time the server shall use for search operation
  63. sizelimit
  64. Maximum number of entries a server should return
  65. (request client-side limit)
  66. serverctrls
  67. list of server-side LDAP controls
  68. clientctrls
  69. list of client-side LDAP controls
  70. """
  71. self._msgId = self._l.search_ext(
  72. searchRoot,searchScope,filterStr,
  73. attrList,attrsOnly,serverctrls,clientctrls,timeout,sizelimit
  74. )
  75. self._afterFirstResult = 1
  76. return # startSearch()
  77. def preProcessing(self):
  78. """
  79. Do anything you want after starting search but
  80. before receiving and processing results
  81. """
  82. def afterFirstResult(self):
  83. """
  84. Do anything you want right after successfully receiving but before
  85. processing first result
  86. """
  87. def postProcessing(self):
  88. """
  89. Do anything you want after receiving and processing all results
  90. """
  91. def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
  92. """
  93. ignoreResultsNumber
  94. Don't process the first ignoreResultsNumber results.
  95. processResultsCount
  96. If non-zero this parameters indicates the number of results
  97. processed is limited to processResultsCount.
  98. timeout
  99. See parameter timeout of ldap.LDAPObject.result()
  100. """
  101. self.preProcessing()
  102. result_counter = 0
  103. end_result_counter = ignoreResultsNumber+processResultsCount
  104. go_ahead = 1
  105. partial = 0
  106. self.beginResultsDropped = 0
  107. self.endResultBreak = result_counter
  108. try:
  109. result_type,result_list = None,None
  110. while go_ahead:
  111. while result_type is None and not result_list:
  112. result_type,result_list,result_msgid,result_serverctrls = self._l.result3(self._msgId,0,timeout)
  113. if self._afterFirstResult:
  114. self.afterFirstResult()
  115. self._afterFirstResult = 0
  116. if not result_list:
  117. break
  118. if result_type not in SEARCH_RESULT_TYPES:
  119. raise WrongResultType(result_type,SEARCH_RESULT_TYPES)
  120. # Loop over list of search results
  121. for result_item in result_list:
  122. if result_counter<ignoreResultsNumber:
  123. self.beginResultsDropped = self.beginResultsDropped+1
  124. elif processResultsCount==0 or result_counter<end_result_counter:
  125. self._processSingleResult(result_type,result_item)
  126. else:
  127. go_ahead = 0 # break-out from while go_ahead
  128. partial = 1
  129. break # break-out from this for-loop
  130. result_counter = result_counter+1
  131. result_type,result_list = None,None
  132. self.endResultBreak = result_counter
  133. finally:
  134. if partial and self._msgId!=None:
  135. self._l.abandon(self._msgId)
  136. self.postProcessing()
  137. return partial # processResults()
  138. def _processSingleResult(self,resultType,resultItem):
  139. """
  140. Process single entry
  141. resultType
  142. result type
  143. resultItem
  144. Single item of a result list
  145. """
  146. pass
  147. class List(AsyncSearchHandler):
  148. """
  149. Class for collecting all search results.
  150. This does not seem to make sense in the first place but think
  151. of retrieving exactly a certain portion of the available search
  152. results.
  153. """
  154. def __init__(self,l):
  155. AsyncSearchHandler.__init__(self,l)
  156. self.allResults = []
  157. def _processSingleResult(self,resultType,resultItem):
  158. self.allResults.append((resultType,resultItem))
  159. class Dict(AsyncSearchHandler):
  160. """
  161. Class for collecting all search results into a dictionary {dn:entry}
  162. """
  163. def __init__(self,l):
  164. AsyncSearchHandler.__init__(self,l)
  165. self.allEntries = {}
  166. def _processSingleResult(self,resultType,resultItem):
  167. if resultType in ENTRY_RESULT_TYPES:
  168. # Search continuations are ignored
  169. dn,entry = resultItem
  170. self.allEntries[dn] = entry
  171. class IndexedDict(Dict):
  172. """
  173. Class for collecting all search results into a dictionary {dn:entry}
  174. and maintain case-sensitive equality indexes to entries
  175. """
  176. def __init__(self,l,indexed_attrs=None):
  177. Dict.__init__(self,l)
  178. self.indexed_attrs = indexed_attrs or ()
  179. self.index = {}.fromkeys(self.indexed_attrs,{})
  180. def _processSingleResult(self,resultType,resultItem):
  181. if resultType in ENTRY_RESULT_TYPES:
  182. # Search continuations are ignored
  183. dn,entry = resultItem
  184. self.allEntries[dn] = entry
  185. for a in self.indexed_attrs:
  186. if a in entry:
  187. for v in entry[a]:
  188. try:
  189. self.index[a][v].append(dn)
  190. except KeyError:
  191. self.index[a][v] = [ dn ]
  192. class FileWriter(AsyncSearchHandler):
  193. """
  194. Class for writing a stream of LDAP search results to a file object
  195. Arguments:
  196. l
  197. LDAPObject instance
  198. f
  199. File object instance where the LDIF data is written to
  200. """
  201. def __init__(self,l,f,headerStr='',footerStr=''):
  202. AsyncSearchHandler.__init__(self,l)
  203. self._f = f
  204. self.headerStr = headerStr
  205. self.footerStr = footerStr
  206. def preProcessing(self):
  207. """
  208. The headerStr is written to output after starting search but
  209. before receiving and processing results.
  210. """
  211. self._f.write(self.headerStr)
  212. def postProcessing(self):
  213. """
  214. The footerStr is written to output after receiving and
  215. processing results.
  216. """
  217. self._f.write(self.footerStr)
  218. class LDIFWriter(FileWriter):
  219. """
  220. Class for writing a stream LDAP search results to a LDIF file
  221. Arguments:
  222. l
  223. LDAPObject instance
  224. writer_obj
  225. Either a file-like object or a ldif.LDIFWriter instance used for output
  226. """
  227. def __init__(self,l,writer_obj,headerStr='',footerStr=''):
  228. if isinstance(writer_obj,ldif.LDIFWriter):
  229. self._ldif_writer = writer_obj
  230. else:
  231. self._ldif_writer = ldif.LDIFWriter(writer_obj)
  232. FileWriter.__init__(self,l,self._ldif_writer._output_file,headerStr,footerStr)
  233. def _processSingleResult(self,resultType,resultItem):
  234. if resultType in ENTRY_RESULT_TYPES:
  235. # Search continuations are ignored
  236. dn,entry = resultItem
  237. self._ldif_writer.unparse(dn,entry)