New upstream version 3.0.0~b4
W. van den Akker
6 years ago
0 | ---------------------------------------------------------------- | |
1 | Released 3.0.0b4 2018-01-10 | |
2 | ||
3 | Changes since 3.0.0b3: | |
4 | ||
5 | Removed support for Python 3.3, which reached its end-of-life 2017-09-29. | |
6 | ||
7 | Lib/ | |
8 | * Make default argument values work under bytes_mode | |
9 | * Update use of map() to use list/set comprehensions instead | |
10 | ||
11 | Test/ | |
12 | * Refactor syncrepl tests to run with bytes_mode | |
13 | ||
14 | Doc/ | |
15 | * Document all_records attribute of LDIFRecordList | |
16 | ||
17 | ||
0 | 18 | ---------------------------------------------------------------- |
1 | 19 | Released 3.0.0b3 2017-12-20 |
2 | 20 |
213 | 213 | * Release the ``sdist`` on PyPI. |
214 | 214 | * Announce the release on the mailing list. |
215 | 215 | Mention the Git hash. |
216 | * Add the release's log from ``CHANGES`` on the `GitHub release page`_. | |
217 | ||
218 | .. _GitHub release page: https://github.com/python-ldap/python-ldap/releases |
119 | 119 | The following software packages are required to be installed |
120 | 120 | on the local system when building python-ldap: |
121 | 121 | |
122 | - `Python`_ version 2.7, or 3.3 or later including its development files | |
122 | - `Python`_ version 2.7, or 3.4 or later including its development files | |
123 | 123 | - C compiler corresponding to your Python version (on Linux, it is usually ``gcc``) |
124 | 124 | - `OpenLDAP`_ client libs version 2.4.11 or later; |
125 | 125 | it is not possible and not supported to build with prior versions. |
999 | 999 | *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. |
1000 | 1000 | |
1001 | 1001 | |
1002 | .. py:method:: LDAPObject.simple_bind([who='' [, cred='' [, serverctrls=None [, clientctrls=None]]]]) -> int | |
1003 | ||
1004 | .. py:method:: LDAPObject.simple_bind_s([who='' [, cred='' [, serverctrls=None [, clientctrls=None]]]]) -> None | |
1002 | .. py:method:: LDAPObject.simple_bind([who=None [, cred=None [, serverctrls=None [, clientctrls=None]]]]) -> int | |
1003 | ||
1004 | .. py:method:: LDAPObject.simple_bind_s([who=None [, cred=None [, serverctrls=None [, clientctrls=None]]]]) -> None | |
1005 | 1005 | |
1006 | 1006 | After an LDAP object is created, and before any other operations can be |
1007 | 1007 | attempted over the connection, a bind operation must be performed. |
1014 | 1014 | |
1015 | 1015 | *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. |
1016 | 1016 | |
1017 | .. versionchanged:: 3.0 | |
1018 | ||
1019 | :meth:`~LDAPObject.simple_bind` and :meth:`~LDAPObject.simple_bind_s` | |
1020 | now accept ``None`` for *who* and *cred*, too. | |
1021 | ||
1017 | 1022 | |
1018 | 1023 | .. py:method:: LDAPObject.search(base, scope [,filterstr='(objectClass=*)' [, attrlist=None [, attrsonly=0]]]) ->int |
1019 | 1024 | |
1067 | 1072 | or :py:meth:`search_ext_s()` (client-side search limit). If non-zero |
1068 | 1073 | not more than *sizelimit* results are returned by the server. |
1069 | 1074 | |
1070 | ||
1075 | .. versionchanged:: 3.0 | |
1076 | ||
1077 | ``filterstr=None`` is equivalent to ``filterstr='(objectClass=*)'``. | |
1078 | ||
1071 | 1079 | |
1072 | 1080 | .. py:method:: LDAPObject.start_tls_s() -> None |
1073 | 1081 |
107 | 107 | for deref_res in decodedValue: |
108 | 108 | deref_attr,deref_val,deref_vals = deref_res[0],deref_res[1],deref_res[2] |
109 | 109 | partial_attrs_dict = { |
110 | str(tv[0]): map(str,tv[1]) | |
110 | str(tv[0]): [str(v) for v in tv[1]] | |
111 | 111 | for tv in deref_vals or [] |
112 | 112 | } |
113 | 113 | try: |
53 | 53 | List or tuple of assertion values. Length must match |
54 | 54 | count of %s in filter_template. |
55 | 55 | """ |
56 | return filter_template % (tuple(map(escape_filter_chars,assertion_values))) | |
56 | return filter_template % tuple(escape_filter_chars(v) for v in assertion_values) | |
57 | 57 | |
58 | 58 | |
59 | 59 | def time_span_filter( |
137 | 137 | Applies escape_func() to all items of `args' and returns a string based |
138 | 138 | on format string `s'. |
139 | 139 | """ |
140 | escape_args = map(escape_func,args) | |
141 | return s % tuple(escape_args) | |
140 | return s % tuple(escape_func(v) for v in args) | |
142 | 141 | |
143 | 142 | |
144 | 143 | def strf_secs(secs): |
405 | 405 | def add_s(self,dn,modlist): |
406 | 406 | return self.add_ext_s(dn,modlist,None,None) |
407 | 407 | |
408 | def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None): | |
408 | def simple_bind(self,who=None,cred=None,serverctrls=None,clientctrls=None): | |
409 | 409 | """ |
410 | 410 | simple_bind([who='' [,cred='']]) -> int |
411 | 411 | """ |
414 | 414 | cred = self._bytesify_input('cred', cred) |
415 | 415 | return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) |
416 | 416 | |
417 | def simple_bind_s(self,who='',cred='',serverctrls=None,clientctrls=None): | |
417 | def simple_bind_s(self,who=None,cred=None,serverctrls=None,clientctrls=None): | |
418 | 418 | """ |
419 | 419 | simple_bind_s([who='' [,cred='']]) -> 4-tuple |
420 | 420 | """ |
747 | 747 | resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls) |
748 | 748 | return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value |
749 | 749 | |
750 | def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): | |
750 | def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): | |
751 | 751 | """ |
752 | 752 | search(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) -> int |
753 | 753 | search_s(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) |
792 | 792 | The amount of search results retrieved can be limited with the |
793 | 793 | sizelimit parameter if non-zero. |
794 | 794 | """ |
795 | ||
795 | 796 | if PY2: |
796 | 797 | base = self._bytesify_input('base', base) |
797 | filterstr = self._bytesify_input('filterstr', filterstr) | |
798 | if filterstr is None: | |
799 | # workaround for default argument, | |
800 | # see https://github.com/python-ldap/python-ldap/issues/147 | |
801 | if self.bytes_mode: | |
802 | filterstr = b'(objectClass=*)' | |
803 | else: | |
804 | filterstr = u'(objectClass=*)' | |
805 | else: | |
806 | filterstr = self._bytesify_input('filterstr', filterstr) | |
798 | 807 | if attrlist is not None: |
799 | 808 | attrlist = tuple(self._bytesify_input('attrlist', a) |
800 | 809 | for a in attrlist) |
810 | else: | |
811 | if filterstr is None: | |
812 | filterstr = '(objectClass=*)' | |
801 | 813 | return self._ldap_call( |
802 | 814 | self._l.search_ext, |
803 | 815 | base,scope,filterstr, |
807 | 819 | timeout,sizelimit, |
808 | 820 | ) |
809 | 821 | |
810 | def search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): | |
822 | def search_ext_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): | |
811 | 823 | msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit) |
812 | 824 | return self.result(msgid,all=1,timeout=timeout)[1] |
813 | 825 | |
814 | def search(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0): | |
826 | def search(self,base,scope,filterstr=None,attrlist=None,attrsonly=0): | |
815 | 827 | return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None) |
816 | 828 | |
817 | def search_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0): | |
829 | def search_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0): | |
818 | 830 | return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout) |
819 | 831 | |
820 | def search_st(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,timeout=-1): | |
832 | def search_st(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,timeout=-1): | |
821 | 833 | return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout) |
822 | 834 | |
823 | 835 | def start_tls_s(self): |
884 | 896 | invalue = RequestControlTuples(invalue) |
885 | 897 | return self._ldap_call(self._l.set_option,option,invalue) |
886 | 898 | |
887 | def search_subschemasubentry_s(self,dn=''): | |
899 | def search_subschemasubentry_s(self,dn=None): | |
888 | 900 | """ |
889 | 901 | Returns the distinguished name of the sub schema sub entry |
890 | 902 | for a part of a DIT specified by dn. |
894 | 906 | |
895 | 907 | Returns: None or text/bytes depending on bytes_mode. |
896 | 908 | """ |
909 | if self.bytes_mode: | |
910 | empty_dn = b'' | |
911 | attrname = b'subschemaSubentry' | |
912 | else: | |
913 | empty_dn = u'' | |
914 | attrname = u'subschemaSubentry' | |
915 | if dn is None: | |
916 | dn = empty_dn | |
897 | 917 | try: |
898 | 918 | r = self.search_s( |
899 | dn,ldap.SCOPE_BASE,'(objectClass=*)',['subschemaSubentry'] | |
919 | dn,ldap.SCOPE_BASE,None,[attrname] | |
900 | 920 | ) |
901 | 921 | except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS): |
902 | 922 | r = [] |
905 | 925 | try: |
906 | 926 | if r: |
907 | 927 | e = ldap.cidict.cidict(r[0][1]) |
908 | search_subschemasubentry_dn = e.get('subschemaSubentry',[None])[0] | |
928 | search_subschemasubentry_dn = e.get(attrname,[None])[0] | |
909 | 929 | if search_subschemasubentry_dn is None: |
910 | 930 | if dn: |
911 | 931 | # Try to find sub schema sub entry in root DSE |
912 | return self.search_subschemasubentry_s(dn='') | |
932 | return self.search_subschemasubentry_s(dn=empty_dn) | |
913 | 933 | else: |
914 | 934 | # If dn was already root DSE we can return here |
915 | 935 | return None |
929 | 949 | r = self.search_ext_s( |
930 | 950 | dn, |
931 | 951 | ldap.SCOPE_BASE, |
932 | filterstr or '(objectClass=*)', | |
952 | filterstr, | |
933 | 953 | attrlist=attrlist, |
934 | 954 | serverctrls=serverctrls, |
935 | 955 | clientctrls=clientctrls, |
944 | 964 | """ |
945 | 965 | Returns the sub schema sub entry's data |
946 | 966 | """ |
967 | if self.bytes_mode: | |
968 | filterstr = b'(objectClass=subschema)' | |
969 | if attrs is None: | |
970 | attrs = [attr.encode('utf-8') for attr in SCHEMA_ATTRS] | |
971 | else: | |
972 | filterstr = u'(objectClass=subschema)' | |
973 | if attrs is None: | |
974 | attrs = SCHEMA_ATTRS | |
947 | 975 | try: |
948 | 976 | subschemasubentry = self.read_s( |
949 | 977 | subschemasubentry_dn, |
950 | filterstr='(objectClass=subschema)', | |
951 | attrlist=attrs or SCHEMA_ATTRS | |
978 | filterstr=filterstr, | |
979 | attrlist=attrs | |
952 | 980 | ) |
953 | 981 | except ldap.NO_SUCH_OBJECT: |
954 | 982 | return None |
955 | 983 | else: |
956 | 984 | return subschemasubentry |
957 | 985 | |
958 | def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1): | |
986 | def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1): | |
959 | 987 | """ |
960 | 988 | Returns a unique entry, raises exception if not unique |
961 | 989 | """ |
963 | 991 | base, |
964 | 992 | scope, |
965 | 993 | filterstr, |
966 | attrlist=attrlist or ['*'], | |
994 | attrlist=attrlist, | |
967 | 995 | attrsonly=attrsonly, |
968 | 996 | serverctrls=serverctrls, |
969 | 997 | clientctrls=clientctrls, |
974 | 1002 | raise NO_UNIQUE_ENTRY('No or non-unique search result for %s' % (repr(filterstr))) |
975 | 1003 | return r[0] |
976 | 1004 | |
977 | def read_rootdse_s(self, filterstr='(objectClass=*)', attrlist=None): | |
1005 | def read_rootdse_s(self, filterstr=None, attrlist=None): | |
978 | 1006 | """ |
979 | 1007 | convenience wrapper around read_s() for reading rootDSE |
980 | 1008 | """ |
1009 | if self.bytes_mode: | |
1010 | base = b'' | |
1011 | attrlist = attrlist or [b'*', b'+'] | |
1012 | else: | |
1013 | base = u'' | |
1014 | attrlist = attrlist or [u'*', u'+'] | |
981 | 1015 | ldap_rootdse = self.read_s( |
982 | '', | |
1016 | base, | |
983 | 1017 | filterstr=filterstr, |
984 | attrlist=attrlist or ['*', '+'], | |
1018 | attrlist=attrlist, | |
985 | 1019 | ) |
986 | 1020 | return ldap_rootdse # read_rootdse_s() |
987 | 1021 | |
990 | 1024 | returns all attribute values of namingContexts in rootDSE |
991 | 1025 | if namingContexts is not present (not readable) then empty list is returned |
992 | 1026 | """ |
1027 | if self.bytes_mode: | |
1028 | name = b'namingContexts' | |
1029 | else: | |
1030 | name = u'namingContexts' | |
993 | 1031 | return self.read_rootdse_s( |
994 | attrlist=['namingContexts'] | |
995 | ).get('namingContexts', []) | |
1032 | attrlist=[name] | |
1033 | ).get(name, []) | |
996 | 1034 | |
997 | 1035 | |
998 | 1036 | class ReconnectLDAPObject(SimpleLDAPObject): |
1068 | 1106 | func(self,*args,**kwargs) |
1069 | 1107 | else: |
1070 | 1108 | # Send explicit anon simple bind request to provoke ldap.SERVER_DOWN in method reconnect() |
1071 | SimpleLDAPObject.simple_bind_s(self,'','') | |
1109 | SimpleLDAPObject.simple_bind_s(self, None, None) | |
1072 | 1110 | |
1073 | 1111 | def _restore_options(self): |
1074 | 1112 | """Restore all recorded options""" |
10 | 10 | |
11 | 11 | def addModlist(entry,ignore_attr_types=None): |
12 | 12 | """Build modify list for call of method LDAPObject.add()""" |
13 | ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) | |
13 | ignore_attr_types = {v.lower() for v in ignore_attr_types or []} | |
14 | 14 | modlist = [] |
15 | 15 | for attrtype in entry.keys(): |
16 | 16 | if attrtype.lower() in ignore_attr_types: |
45 | 45 | List of attribute type names for which comparison will be made |
46 | 46 | case-insensitive |
47 | 47 | """ |
48 | ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) | |
49 | case_ignore_attr_types = set(map(str.lower,case_ignore_attr_types or [])) | |
48 | ignore_attr_types = {v.lower() for v in ignore_attr_types or []} | |
49 | case_ignore_attr_types = {v.lower() for v in case_ignore_attr_types or []} | |
50 | 50 | modlist = [] |
51 | 51 | attrtype_lower_map = {} |
52 | 52 | for a in old_entry.keys(): |
72 | 72 | replace_attr_value = len(old_value)!=len(new_value) |
73 | 73 | if not replace_attr_value: |
74 | 74 | if attrtype_lower in case_ignore_attr_types: |
75 | norm_func = str.lower | |
76 | old_value_set = set(map(str.lower,old_value)) | |
77 | new_value_set = set(map(str.lower,new_value)) | |
75 | old_value_set = {v.lower() for v in old_value} | |
76 | new_value_set = {v.lower() for v in new_value} | |
78 | 77 | else: |
79 | 78 | old_value_set = set(old_value) |
80 | 79 | new_value_set = set(new_value) |
1 | 1 | """ |
2 | 2 | meta attributes for packaging which does not import any dependencies |
3 | 3 | """ |
4 | __version__ = '3.0.0b3' | |
4 | __version__ = '3.0.0b4' | |
5 | 5 | __author__ = u'python-ldap project' |
6 | 6 | __license__ = 'Python style' |
126 | 126 | This list of strings contains NAMEs or OIDs of object classes |
127 | 127 | this object class is derived from |
128 | 128 | """ |
129 | schema_attribute = 'objectClasses' | |
129 | schema_attribute = u'objectClasses' | |
130 | 130 | token_defaults = { |
131 | 131 | 'NAME':(()), |
132 | 132 | 'DESC':(None,), |
224 | 224 | This list of strings contains NAMEs or OIDs of attribute types |
225 | 225 | this attribute type is derived from |
226 | 226 | """ |
227 | schema_attribute = 'attributeTypes' | |
227 | schema_attribute = u'attributeTypes' | |
228 | 228 | token_defaults = { |
229 | 229 | 'NAME':(()), |
230 | 230 | 'DESC':(None,), |
318 | 318 | Integer flag (0 or 1) indicating whether the attribute type is marked |
319 | 319 | as not human-readable (X-NOT-HUMAN-READABLE) |
320 | 320 | """ |
321 | schema_attribute = 'ldapSyntaxes' | |
321 | schema_attribute = u'ldapSyntaxes' | |
322 | 322 | token_defaults = { |
323 | 323 | 'DESC':(None,), |
324 | 324 | 'X-NOT-HUMAN-READABLE':(None,), |
366 | 366 | syntax |
367 | 367 | String contains OID of the LDAP syntax this matching rule is usable with |
368 | 368 | """ |
369 | schema_attribute = 'matchingRules' | |
369 | schema_attribute = u'matchingRules' | |
370 | 370 | token_defaults = { |
371 | 371 | 'NAME':(()), |
372 | 372 | 'DESC':(None,), |
412 | 412 | This list of strings contains NAMEs or OIDs of attribute types |
413 | 413 | for which this matching rule is used |
414 | 414 | """ |
415 | schema_attribute = 'matchingRuleUse' | |
415 | schema_attribute = u'matchingRuleUse' | |
416 | 416 | token_defaults = { |
417 | 417 | 'NAME':(()), |
418 | 418 | 'DESC':(None,), |
469 | 469 | This list of strings contains NAMEs or OIDs of attributes which |
470 | 470 | may not be present in an entry of the object class |
471 | 471 | """ |
472 | schema_attribute = 'dITContentRules' | |
472 | schema_attribute = u'dITContentRules' | |
473 | 473 | token_defaults = { |
474 | 474 | 'NAME':(()), |
475 | 475 | 'DESC':(None,), |
526 | 526 | List of strings with NAMEs or OIDs of allowed structural object classes |
527 | 527 | of superior entries in the DIT |
528 | 528 | """ |
529 | schema_attribute = 'dITStructureRules' | |
529 | schema_attribute = u'dITStructureRules' | |
530 | 530 | |
531 | 531 | token_defaults = { |
532 | 532 | 'NAME':(()), |
590 | 590 | This list of strings contains NAMEs or OIDs of additional attributes |
591 | 591 | an RDN may contain |
592 | 592 | """ |
593 | schema_attribute = 'nameForms' | |
593 | schema_attribute = u'nameForms' | |
594 | 594 | token_defaults = { |
595 | 595 | 'NAME':(()), |
596 | 596 | 'DESC':(None,), |
3 | 3 | See https://www.python-ldap.org/ for details. |
4 | 4 | """ |
5 | 5 | |
6 | __version__ = '3.0.0b3' | |
6 | __version__ = '3.0.0b4' | |
7 | 7 | |
8 | 8 | __all__ = [ |
9 | 9 | # constants |
157 | 157 | ] |
158 | 158 | |
159 | 159 | def __str__(self): |
160 | return ','.join(map(str,self.values())) | |
160 | return ','.join(str(v) for v in self.values()) | |
161 | 161 | |
162 | 162 | def __repr__(self): |
163 | 163 | return '<%s.%s instance at %s: %s>' % ( |
5 | 5 | |
6 | 6 | from __future__ import unicode_literals |
7 | 7 | |
8 | __version__ = '3.0.0b3' | |
8 | __version__ = '3.0.0b4' | |
9 | 9 | |
10 | 10 | __all__ = [ |
11 | 11 | # constants |
576 | 576 | |
577 | 577 | class LDIFRecordList(LDIFParser): |
578 | 578 | """ |
579 | Collect all records of LDIF input into a single list. | |
580 | of 2-tuples (dn,entry). It can be a memory hog! | |
579 | Collect all records of a LDIF file. It can be a memory hog! | |
580 | ||
581 | Records are stored in :attr:`.all_records` as a single list | |
582 | of 2-tuples (dn, entry), after calling :meth:`.parse`. | |
581 | 583 | """ |
582 | 584 | |
583 | 585 | def __init__( |
585 | 587 | input_file, |
586 | 588 | ignored_attr_types=None,max_entries=0,process_url_schemes=None |
587 | 589 | ): |
588 | """ | |
589 | See LDIFParser.__init__() | |
590 | ||
591 | Additional Parameters: | |
592 | all_records | |
593 | List instance for storing parsed records | |
594 | """ | |
595 | 590 | LDIFParser.__init__(self,input_file,ignored_attr_types,max_entries,process_url_schemes) |
591 | ||
592 | #: List storing parsed records. | |
596 | 593 | self.all_records = [] |
597 | 594 | self.all_modify_changes = [] |
598 | 595 | |
599 | 596 | def handle(self,dn,entry): |
600 | 597 | """ |
601 | Append single record to dictionary of all records. | |
598 | Append a single record to the list of all records (:attr:`.all_records`). | |
602 | 599 | """ |
603 | 600 | self.all_records.append((dn,entry)) |
604 | 601 |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: python-ldap |
2 | Version: 3.0.0b3 | |
2 | Version: 3.0.0b4 | |
3 | 3 | Summary: Python modules for implementing LDAP clients |
4 | 4 | Home-page: https://www.python-ldap.org/ |
5 | 5 | Author: python-ldap project |
27 | 27 | Classifier: Programming Language :: Python :: 2 |
28 | 28 | Classifier: Programming Language :: Python :: 2.7 |
29 | 29 | Classifier: Programming Language :: Python :: 3 |
30 | Classifier: Programming Language :: Python :: 3.3 | |
31 | 30 | Classifier: Programming Language :: Python :: 3.4 |
32 | 31 | Classifier: Programming Language :: Python :: 3.5 |
33 | 32 | Classifier: Programming Language :: Python :: 3.6 |
36 | 35 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
37 | 36 | Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP |
38 | 37 | Classifier: License :: OSI Approved :: Python Software Foundation License |
39 | Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.* | |
38 | Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* |
4 | 4 | See https://www.python-ldap.org/ for details. |
5 | 5 | """ |
6 | 6 | |
7 | __version__ = '3.0.0b3' | |
7 | __version__ = '3.0.0b4' | |
8 | 8 | |
9 | 9 | from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler |
10 | 10 | from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls |
498 | 498 | LDAPControl** client_ldcs = NULL; |
499 | 499 | struct berval cred; |
500 | 500 | |
501 | if (!PyArg_ParseTuple( args, "ss#|OO", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; | |
501 | if (!PyArg_ParseTuple( args, "zz#|OO", &who, &cred.bv_val, &cred_len, &serverctrls, &clientctrls )) return NULL; | |
502 | 502 | cred.bv_len = (ber_len_t) cred_len; |
503 | 503 | |
504 | 504 | if (not_valid(self)) return NULL; |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: python-ldap |
2 | Version: 3.0.0b3 | |
2 | Version: 3.0.0b4 | |
3 | 3 | Summary: Python modules for implementing LDAP clients |
4 | 4 | Home-page: https://www.python-ldap.org/ |
5 | 5 | Author: python-ldap project |
27 | 27 | Classifier: Programming Language :: Python :: 2 |
28 | 28 | Classifier: Programming Language :: Python :: 2.7 |
29 | 29 | Classifier: Programming Language :: Python :: 3 |
30 | Classifier: Programming Language :: Python :: 3.3 | |
31 | 30 | Classifier: Programming Language :: Python :: 3.4 |
32 | 31 | Classifier: Programming Language :: Python :: 3.5 |
33 | 32 | Classifier: Programming Language :: Python :: 3.6 |
36 | 35 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
37 | 36 | Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP |
38 | 37 | Classifier: License :: OSI Approved :: Python Software Foundation License |
39 | Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.* | |
38 | Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* |
6 | 6 | |
7 | 7 | |
8 | 8 | import os |
9 | import shelve | |
10 | import sys | |
9 | 11 | import unittest |
10 | import shelve | |
12 | ||
13 | if sys.version_info[0] <= 2: | |
14 | PY2 = True | |
15 | else: | |
16 | PY2 = False | |
11 | 17 | |
12 | 18 | from slapdtest import SlapdObject, SlapdTestCase |
13 | 19 | |
124 | 130 | Needs to be separate, because once an LDAP client starts a syncrepl |
125 | 131 | search, it can't be used for anything else. |
126 | 132 | """ |
127 | server_class = SyncreplProvider | |
128 | ||
129 | def __init__(self, uri, dn, password, storage=None): | |
133 | ||
134 | def __init__(self, uri, dn, password, storage=None, filterstr=None, | |
135 | **kwargs): | |
130 | 136 | """ |
131 | 137 | Set up our object by creating a search client, connecting, and binding. |
132 | 138 | """ |
145 | 151 | self.data['cookie'] = None |
146 | 152 | self.present = [] |
147 | 153 | self.refresh_done = False |
148 | ||
149 | SimpleLDAPObject.__init__(self, uri) | |
154 | self.filterstr = filterstr | |
155 | ||
156 | SimpleLDAPObject.__init__(self, uri, **kwargs) | |
150 | 157 | self.simple_bind_s(dn, password) |
151 | ||
152 | 158 | |
153 | 159 | def unbind_s(self): |
154 | 160 | """ |
160 | 166 | self.dn_attrs.close() |
161 | 167 | SimpleLDAPObject.unbind_s(self) |
162 | 168 | |
163 | ||
164 | 169 | def search(self, search_base, search_mode): |
165 | 170 | """ |
166 | 171 | Start a syncrepl search operation, given a base DN and search mode. |
169 | 174 | search_base, |
170 | 175 | ldap.SCOPE_SUBTREE, |
171 | 176 | mode=search_mode, |
172 | filterstr='(objectClass=*)' | |
173 | ) | |
174 | ||
177 | filterstr=self.filterstr | |
178 | ) | |
175 | 179 | |
176 | 180 | def cancel(self): |
177 | 181 | """ |
178 | 182 | A simple wrapper to call parent class with syncrepl search ID. |
179 | 183 | """ |
180 | 184 | SimpleLDAPObject.cancel(self, self.search_id) |
181 | ||
182 | 185 | |
183 | 186 | def poll(self, timeout=None, all=0): |
184 | 187 | """ |
190 | 193 | all=all |
191 | 194 | ) |
192 | 195 | |
193 | ||
194 | 196 | def syncrepl_get_cookie(self): |
195 | 197 | """ |
196 | 198 | Pull cookie from storage, if one exists. |
197 | 199 | """ |
198 | 200 | return self.data['cookie'] |
199 | 201 | |
200 | ||
201 | 202 | def syncrepl_set_cookie(self, cookie): |
202 | 203 | """ |
203 | 204 | Update stored cookie. |
204 | 205 | """ |
205 | 206 | self.data['cookie'] = cookie |
206 | 207 | |
207 | ||
208 | 208 | def syncrepl_refreshdone(self): |
209 | 209 | """ |
210 | 210 | Just update a variable. |
211 | 211 | """ |
212 | 212 | self.refresh_done = True |
213 | ||
214 | 213 | |
215 | 214 | def syncrepl_delete(self, uuids): |
216 | 215 | """ |
219 | 218 | for uuid in uuids: |
220 | 219 | del self.dn_attrs[self.uuid_dn[uuid]] |
221 | 220 | del self.uuid_dn[uuid] |
222 | ||
223 | 221 | |
224 | 222 | def syncrepl_entry(self, dn, attrs, uuid): |
225 | 223 | """ |
235 | 233 | self.uuid_dn[uuid] = dn |
236 | 234 | self.dn_attrs[dn] = attrs |
237 | 235 | |
238 | ||
239 | 236 | def syncrepl_present(self, uuids, refreshDeletes=False): |
240 | 237 | """ |
241 | 238 | The 'present' message from the LDAP server is the most complicated |
261 | 258 | pass |
262 | 259 | |
263 | 260 | |
264 | class Test00_Syncrepl(SlapdTestCase): | |
261 | class BaseSyncreplTests(object): | |
265 | 262 | """ |
266 | 263 | This is a test of all the basic Syncrepl operations. It covers starting a |
267 | 264 | search (both types of search), doing the refresh part of the search, |
274 | 271 | |
275 | 272 | @classmethod |
276 | 273 | def setUpClass(cls): |
277 | super(Test00_Syncrepl, cls).setUpClass() | |
274 | super(BaseSyncreplTests, cls).setUpClass() | |
278 | 275 | # insert some Foo* objects via ldapadd |
279 | 276 | cls.server.ldapadd( |
280 | 277 | LDIF_TEMPLATE % { |
286 | 283 | } |
287 | 284 | ) |
288 | 285 | |
289 | ||
290 | 286 | def setUp(self): |
291 | try: | |
292 | self._ldap_conn | |
293 | except AttributeError: | |
294 | # open local LDAP connection | |
295 | self._ldap_conn = self._open_ldap_conn() | |
296 | ||
287 | super(BaseSyncreplTests, self).setUp() | |
288 | self.tester = None | |
289 | self.suffix = None | |
297 | 290 | |
298 | 291 | def tearDown(self): |
299 | 292 | self.tester.unbind_s() |
300 | ||
293 | super(BaseSyncreplTests, self).tearDown() | |
294 | ||
295 | def create_client(self): | |
296 | raise NotImplementedError | |
301 | 297 | |
302 | 298 | def test_refreshOnly_search(self): |
303 | 299 | ''' |
304 | 300 | Test to see if we can initialize a syncrepl search. |
305 | 301 | ''' |
306 | self.tester = SyncreplClient( | |
307 | self.server.ldap_uri, | |
308 | self.server.root_dn, | |
309 | self.server.root_pw | |
310 | ) | |
311 | self.tester.search( | |
312 | self.server.suffix, | |
302 | self.tester.search( | |
303 | self.suffix, | |
313 | 304 | 'refreshOnly' |
314 | 305 | ) |
315 | 306 | |
316 | ||
317 | 307 | def test_refreshAndPersist_search(self): |
318 | self.tester = SyncreplClient( | |
319 | self.server.ldap_uri, | |
320 | self.server.root_dn, | |
321 | self.server.root_pw | |
322 | ) | |
323 | self.tester.search( | |
324 | self.server.suffix, | |
308 | self.tester.search( | |
309 | self.suffix, | |
325 | 310 | 'refreshAndPersist' |
326 | 311 | ) |
327 | 312 | |
328 | ||
329 | 313 | def test_refreshOnly_poll_full(self): |
330 | 314 | """ |
331 | 315 | Test doing a full refresh cycle, and check what we got. |
332 | 316 | """ |
333 | self.tester = SyncreplClient( | |
334 | self.server.ldap_uri, | |
335 | self.server.root_dn, | |
336 | self.server.root_pw | |
337 | ) | |
338 | self.tester.search( | |
339 | self.server.suffix, | |
317 | self.tester.search( | |
318 | self.suffix, | |
340 | 319 | 'refreshOnly' |
341 | 320 | ) |
342 | 321 | poll_result = self.tester.poll( |
346 | 325 | self.assertFalse(poll_result) |
347 | 326 | self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) |
348 | 327 | |
349 | ||
350 | 328 | def test_refreshAndPersist_poll_only(self): |
351 | 329 | """ |
352 | 330 | Test the refresh part of refresh-and-persist, and check what we got. |
353 | 331 | """ |
354 | self.tester = SyncreplClient( | |
355 | self.server.ldap_uri, | |
356 | self.server.root_dn, | |
357 | self.server.root_pw | |
358 | ) | |
359 | self.tester.search( | |
360 | self.server.suffix, | |
332 | self.tester.search( | |
333 | self.suffix, | |
361 | 334 | 'refreshAndPersist' |
362 | 335 | ) |
363 | 336 | |
371 | 344 | |
372 | 345 | self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) |
373 | 346 | |
374 | ||
375 | 347 | def test_refreshAndPersist_timeout(self): |
376 | 348 | """ |
377 | 349 | Make sure refreshAndPersist can handle a search with timeouts. |
378 | 350 | """ |
379 | self.tester = SyncreplClient( | |
380 | self.server.ldap_uri, | |
381 | self.server.root_dn, | |
382 | self.server.root_pw | |
383 | ) | |
384 | self.tester.search( | |
385 | self.server.suffix, | |
351 | self.tester.search( | |
352 | self.suffix, | |
386 | 353 | 'refreshAndPersist' |
387 | 354 | ) |
388 | 355 | |
406 | 373 | timeout=1 |
407 | 374 | ) |
408 | 375 | |
409 | ||
410 | 376 | def test_refreshAndPersist_cancelled(self): |
411 | 377 | """ |
412 | 378 | Make sure refreshAndPersist can handle cancelling a syncrepl search. |
413 | 379 | """ |
414 | self.tester = SyncreplClient( | |
415 | self.server.ldap_uri, | |
416 | self.server.root_dn, | |
417 | self.server.root_pw | |
418 | ) | |
419 | self.tester.search( | |
420 | self.server.suffix, | |
380 | self.tester.search( | |
381 | self.suffix, | |
421 | 382 | 'refreshAndPersist' |
422 | 383 | ) |
423 | 384 | |
462 | 423 | # should pick it up during the persist phase. |
463 | 424 | |
464 | 425 | |
426 | class TestSyncrepl(BaseSyncreplTests, SlapdTestCase): | |
427 | def setUp(self): | |
428 | super(TestSyncrepl, self).setUp() | |
429 | self.tester = SyncreplClient( | |
430 | self.server.ldap_uri, | |
431 | self.server.root_dn, | |
432 | self.server.root_pw, | |
433 | filterstr=u'(objectClass=*)', | |
434 | bytes_mode=False | |
435 | ) | |
436 | self.suffix = self.server.suffix | |
437 | ||
438 | ||
439 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") | |
440 | class TestSyncreplBytesMode(BaseSyncreplTests, SlapdTestCase): | |
441 | def setUp(self): | |
442 | super(TestSyncreplBytesMode, self).setUp() | |
443 | self.tester = SyncreplClient( | |
444 | self.server.ldap_uri, | |
445 | self.server.root_dn.encode('utf-8'), | |
446 | self.server.root_pw.encode('utf-8'), | |
447 | filterstr=b'(objectClass=*)', | |
448 | bytes_mode=True | |
449 | ) | |
450 | self.suffix = self.server.suffix.encode('utf-8') | |
451 | ||
452 | ||
465 | 453 | if __name__ == '__main__': |
466 | 454 | unittest.main() |
161 | 161 | self.assertEqual(type(value), bytes) |
162 | 162 | |
163 | 163 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") |
164 | def test_bytesmode_search_defaults(self): | |
165 | l = self._get_bytes_ldapobject() | |
166 | base = 'cn=Foo1,' + self.server.suffix | |
167 | kwargs = dict( | |
168 | base=base.encode('utf-8'), | |
169 | scope=ldap.SCOPE_SUBTREE, | |
170 | # filterstr=b'(objectClass=*)' | |
171 | ) | |
172 | expected = [ | |
173 | ( | |
174 | base, | |
175 | {'cn': [b'Foo1'], 'objectClass': [b'organizationalRole']} | |
176 | ), | |
177 | ] | |
178 | ||
179 | result = l.search_s(**kwargs) | |
180 | self.assertEqual(result, expected) | |
181 | result = l.search_st(**kwargs) | |
182 | self.assertEqual(result, expected) | |
183 | result = l.search_ext_s(**kwargs) | |
184 | self.assertEqual(result, expected) | |
185 | ||
186 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") | |
164 | 187 | def test_unset_bytesmode_search_warns_bytes(self): |
165 | 188 | l = self._get_bytes_ldapobject(explicit=False) |
166 | 189 | base = self.server.suffix |
262 | 285 | [('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': [b'Foo4']})] |
263 | 286 | ) |
264 | 287 | |
288 | def test_find_unique_entry(self): | |
289 | result = self._ldap_conn.find_unique_entry( | |
290 | self.server.suffix, | |
291 | ldap.SCOPE_SUBTREE, | |
292 | '(cn=Foo4)', | |
293 | ['cn'], | |
294 | ) | |
295 | self.assertEqual( | |
296 | result, | |
297 | ('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': [b'Foo4']}) | |
298 | ) | |
299 | with self.assertRaises(ldap.SIZELIMIT_EXCEEDED): | |
300 | # > 2 entries returned | |
301 | self._ldap_conn.find_unique_entry( | |
302 | self.server.suffix, | |
303 | ldap.SCOPE_ONELEVEL, | |
304 | '(cn=Foo*)', | |
305 | ['*'], | |
306 | ) | |
307 | with self.assertRaises(ldap.NO_UNIQUE_ENTRY): | |
308 | # 0 entries returned | |
309 | self._ldap_conn.find_unique_entry( | |
310 | self.server.suffix, | |
311 | ldap.SCOPE_ONELEVEL, | |
312 | '(cn=Bar*)', | |
313 | ['*'], | |
314 | ) | |
315 | ||
265 | 316 | def test_search_subschema(self): |
266 | 317 | l = self._ldap_conn |
267 | 318 | dn = l.search_subschemasubentry_s() |
268 | 319 | self.assertIsInstance(dn, text_type) |
269 | 320 | self.assertEqual(dn, "cn=Subschema") |
321 | subschema = l.read_subschemasubentry_s(dn) | |
322 | self.assertIsInstance(subschema, dict) | |
323 | self.assertEqual( | |
324 | sorted(subschema), | |
325 | [ | |
326 | u'attributeTypes', | |
327 | u'ldapSyntaxes', | |
328 | u'matchingRuleUse', | |
329 | u'matchingRules', | |
330 | u'objectClasses' | |
331 | ] | |
332 | ) | |
270 | 333 | |
271 | 334 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") |
272 | 335 | def test_search_subschema_have_bytes(self): |
273 | l = self._get_bytes_ldapobject(explicit=False) | |
336 | l = self._get_bytes_ldapobject() | |
274 | 337 | dn = l.search_subschemasubentry_s() |
275 | 338 | self.assertIsInstance(dn, bytes) |
276 | 339 | self.assertEqual(dn, b"cn=Subschema") |
340 | subschema = l.read_subschemasubentry_s(dn) | |
341 | self.assertIsInstance(subschema, dict) | |
342 | self.assertEqual( | |
343 | sorted(subschema), | |
344 | [ | |
345 | b'attributeTypes', | |
346 | b'ldapSyntaxes', | |
347 | b'matchingRuleUse', | |
348 | b'matchingRules', | |
349 | b'objectClasses' | |
350 | ] | |
351 | ) | |
277 | 352 | |
278 | 353 | def test004_errno107(self): |
279 | 354 | l = self.ldap_object_class('ldap://127.0.0.1:42') |
324 | 399 | issubclass(cls, other), |
325 | 400 | cls.__mro__ |
326 | 401 | ) |
402 | ||
403 | def test_simple_bind_noarg(self): | |
404 | l = self.ldap_object_class(self.server.ldap_uri) | |
405 | l.simple_bind_s() | |
406 | self.assertEqual(l.whoami_s(), u'') | |
407 | l = self.ldap_object_class(self.server.ldap_uri) | |
408 | l.simple_bind_s(None, None) | |
409 | self.assertEqual(l.whoami_s(), u'') | |
327 | 410 | |
328 | 411 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") |
329 | 412 | def test_ldapbyteswarning(self): |
432 | 515 | l.simple_bind_s(self.server.root_dn, self.server.root_pw) |
433 | 516 | self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn) |
434 | 517 | |
518 | def test_dse(self): | |
519 | dse = self._ldap_conn.read_rootdse_s() | |
520 | self.assertIsInstance(dse, dict) | |
521 | self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) | |
522 | self.assertEqual( | |
523 | sorted(dse), | |
524 | [u'configContext', u'entryDN', u'namingContexts', u'objectClass', | |
525 | u'structuralObjectClass', u'subschemaSubentry', | |
526 | u'supportedControl', u'supportedExtension', u'supportedFeatures', | |
527 | u'supportedLDAPVersion', u'supportedSASLMechanisms'] | |
528 | ) | |
529 | self.assertEqual( | |
530 | self._ldap_conn.get_naming_contexts(), | |
531 | [self.server.suffix.encode('utf-8')] | |
532 | ) | |
533 | ||
534 | @unittest.skipUnless(PY2, "no bytes_mode under Py3") | |
535 | def test_dse_bytes(self): | |
536 | l = self._get_bytes_ldapobject() | |
537 | dse = l.read_rootdse_s() | |
538 | self.assertIsInstance(dse, dict) | |
539 | self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) | |
540 | self.assertEqual( | |
541 | l.get_naming_contexts(), | |
542 | [self.server.suffix.encode('utf-8')] | |
543 | ) | |
544 | ||
435 | 545 | |
436 | 546 | class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): |
437 | 547 | """ |
8 | 8 | |
9 | 9 | if sys.version_info[0] == 2 and sys.version_info[1] < 7: |
10 | 10 | raise RuntimeError('This software requires Python 2.7 or 3.x.') |
11 | if sys.version_info[0] >= 3 and sys.version_info < (3, 3): | |
12 | raise RuntimeError('The C API from Python 3.3+ is required.') | |
11 | if sys.version_info[0] >= 3 and sys.version_info < (3, 4): | |
12 | raise RuntimeError('The C API from Python 3.4+ is required.') | |
13 | 13 | |
14 | 14 | if sys.version_info[0] >= 3: |
15 | 15 | from configparser import ConfigParser |
90 | 90 | 'Programming Language :: Python :: 2', |
91 | 91 | 'Programming Language :: Python :: 2.7', |
92 | 92 | 'Programming Language :: Python :: 3', |
93 | 'Programming Language :: Python :: 3.3', | |
94 | 93 | 'Programming Language :: Python :: 3.4', |
95 | 94 | 'Programming Language :: Python :: 3.5', |
96 | 95 | 'Programming Language :: Python :: 3.6', |
167 | 166 | 'pyasn1_modules >= 0.1.5', |
168 | 167 | ], |
169 | 168 | zip_safe=False, |
170 | python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', | |
169 | python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', | |
171 | 170 | test_suite = 'Tests', |
172 | 171 | ) |
4 | 4 | |
5 | 5 | [tox] |
6 | 6 | # Note: when updating Python versions, also change setup.py and .travis.yml |
7 | envlist = py27,py33,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report | |
7 | envlist = py27,py34,py35,py36,{py2,py3}-nosasltls,doc,coverage-report | |
8 | 8 | minver = 1.8 |
9 | 9 | |
10 | 10 | [testenv] |
12 | 12 | passenv = WITH_GCOV |
13 | 13 | # - Enable BytesWarning |
14 | 14 | # - Turn all warnings into exceptions. |
15 | # - 'ignore:the imp module is deprecated' is required to ignore import of | |
16 | # 'imp' in distutils. Python 3.3 and 3.4 use PendingDeprecationWarning. | |
15 | # - 'ignore:the imp module is deprecated' is required to ignore import of 'imp' | |
16 | # in distutils. Python < 3.6 use PendingDeprecationWarning; Python >= 3.6 use | |
17 | # DeprecationWarning. | |
17 | 18 | commands = {envpython} -bb -Werror \ |
18 | 19 | "-Wignore:the imp module is deprecated:DeprecationWarning" \ |
19 | 20 | "-Wignore:the imp module is deprecated:PendingDeprecationWarning" \ |