Merge from upstream CVS HEAD
This commit is contained in:
19
CHANGES
19
CHANGES
@@ -1,3 +1,20 @@
|
||||
----------------------------------------------------------------
|
||||
Released 2.4.21 2015-07-xx
|
||||
|
||||
Changes since 2.4.20:
|
||||
|
||||
Lib/
|
||||
* LDAPObject.read_s() now returns None instead of raising
|
||||
ldap.NO_SUCH_OBJECT in case the search operation returned emtpy result.
|
||||
* ldap.resiter.ResultProcessor.allresults() now takes new key-word
|
||||
argument add_ctrls which is internally passed to LDAPObject.result4()
|
||||
and lets the method also return response control along with the search
|
||||
results.
|
||||
* Added ldap.controls.deref implementing support for dereference control
|
||||
|
||||
Tests/
|
||||
* Unit tests for module ldif (thanks to Petr Viktorin)
|
||||
|
||||
----------------------------------------------------------------
|
||||
Released 2.4.20 2015-07-07
|
||||
|
||||
@@ -1178,4 +1195,4 @@ Released 2.0.0pre02 2002-02-01
|
||||
----------------------------------------------------------------
|
||||
Released 1.10alpha3 2000-09-19
|
||||
|
||||
$Id: CHANGES,v 1.350 2015/07/07 13:21:42 stroeder Exp $
|
||||
$Id: CHANGES,v 1.355 2015/09/19 13:38:30 stroeder Exp $
|
||||
|
||||
50
Demo/pyasn1/derefcontrol.py
Normal file
50
Demo/pyasn1/derefcontrol.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
This sample script demonstrates the use of the dereference control
|
||||
(see https://tools.ietf.org/html/draft-masarati-ldap-deref)
|
||||
|
||||
Requires module pyasn1 (see http://pyasn1.sourceforge.net/)
|
||||
"""
|
||||
|
||||
import pprint,ldap,ldap.modlist,ldap.resiter
|
||||
|
||||
from ldap.controls.deref import DereferenceControl
|
||||
|
||||
uri = "ldap://ipa.demo1.freeipa.org"
|
||||
|
||||
class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor):
|
||||
pass
|
||||
|
||||
|
||||
l = MyLDAPObject(uri,trace_level=0)
|
||||
l.simple_bind_s('uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org','Secret123')
|
||||
|
||||
dc = DereferenceControl(
|
||||
True,
|
||||
{
|
||||
'member':[
|
||||
'uid',
|
||||
'description',
|
||||
'cn',
|
||||
'mail',
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
print 'pyasn1 output of request control:'
|
||||
print dc._derefSpecs().prettyPrint()
|
||||
|
||||
msg_id = l.search_ext(
|
||||
'dc=demo1,dc=freeipa,dc=org',
|
||||
ldap.SCOPE_SUBTREE,
|
||||
'(objectClass=groupOfNames)',
|
||||
attrlist=['cn','objectClass','member','description'],
|
||||
serverctrls = [dc]
|
||||
)
|
||||
|
||||
for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id,add_ctrls=1):
|
||||
for dn,entry,deref_control in res_data:
|
||||
# process dn and entry
|
||||
print dn,entry['objectClass']
|
||||
if deref_control:
|
||||
pprint.pprint(deref_control[0].derefRes)
|
||||
@@ -4,13 +4,13 @@ dsml - generate and parse DSMLv1 data
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
$Id: dsml.py,v 1.37 2015/06/05 21:04:58 stroeder Exp $
|
||||
$Id: dsml.py,v 1.38 2015/08/08 13:36:30 stroeder Exp $
|
||||
|
||||
Python compability note:
|
||||
Tested with Python 2.0+.
|
||||
"""
|
||||
|
||||
__version__ = '2.4.20'
|
||||
__version__ = '2.4.21'
|
||||
|
||||
import string,base64
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ ldap - base module
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
$Id: __init__.py,v 1.97 2015/06/05 21:04:58 stroeder Exp $
|
||||
$Id: __init__.py,v 1.98 2015/08/08 13:36:30 stroeder Exp $
|
||||
"""
|
||||
|
||||
# This is also the overall release version number
|
||||
|
||||
__version__ = '2.4.20'
|
||||
__version__ = '2.4.21'
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
122
Lib/ldap/controls/deref.py
Normal file
122
Lib/ldap/controls/deref.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.deref - classes for
|
||||
(see https://tools.ietf.org/html/draft-masarati-ldap-deref)
|
||||
|
||||
See http://www.python-ldap.org/ for project details.
|
||||
|
||||
$Id: deref.py,v 1.2 2015/09/19 13:41:01 stroeder Exp $
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'DEREF_CONTROL_OID',
|
||||
'DereferenceControl',
|
||||
]
|
||||
|
||||
import ldap.controls
|
||||
from ldap.controls import LDAPControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
import pyasn1_modules.rfc2251
|
||||
from pyasn1.type import namedtype,univ,tag
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
from pyasn1_modules.rfc2251 import LDAPDN,AttributeDescription,AttributeDescriptionList,AttributeValue
|
||||
|
||||
|
||||
DEREF_CONTROL_OID = '1.3.6.1.4.1.4203.666.5.16'
|
||||
|
||||
|
||||
# Request types
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# For compability with ASN.1 declaration in I-D
|
||||
AttributeList = AttributeDescriptionList
|
||||
|
||||
class DerefSpec(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'derefAttr',
|
||||
AttributeDescription()
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'attributes',
|
||||
AttributeList()
|
||||
),
|
||||
)
|
||||
|
||||
class DerefSpecs(univ.SequenceOf):
|
||||
componentType = DerefSpec()
|
||||
|
||||
# Response types
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AttributeValues(univ.SetOf):
|
||||
componentType = AttributeValue()
|
||||
|
||||
|
||||
class PartialAttribute(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('type', AttributeDescription()),
|
||||
namedtype.NamedType('vals', AttributeValues()),
|
||||
)
|
||||
|
||||
|
||||
class PartialAttributeList(univ.SequenceOf):
|
||||
componentType = PartialAttribute()
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassContext,tag.tagFormatConstructed,0)
|
||||
)
|
||||
|
||||
|
||||
class DerefRes(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('derefAttr', AttributeDescription()),
|
||||
namedtype.NamedType('derefVal', LDAPDN()),
|
||||
namedtype.OptionalNamedType('attrVals', PartialAttributeList()),
|
||||
)
|
||||
|
||||
|
||||
class DerefResultControlValue(univ.SequenceOf):
|
||||
componentType = DerefRes()
|
||||
|
||||
|
||||
class DereferenceControl(LDAPControl):
|
||||
controlType = DEREF_CONTROL_OID
|
||||
|
||||
def __init__(self,criticality=False,derefSpecs=None):
|
||||
LDAPControl.__init__(self,self.controlType,criticality)
|
||||
self.derefSpecs = derefSpecs or {}
|
||||
|
||||
def _derefSpecs(self):
|
||||
deref_specs = DerefSpecs()
|
||||
i = 0
|
||||
for deref_attr,deref_attribute_names in self.derefSpecs.items():
|
||||
deref_spec = DerefSpec()
|
||||
deref_attributes = AttributeList()
|
||||
for j in range(len(deref_attribute_names)):
|
||||
deref_attributes.setComponentByPosition(j,deref_attribute_names[j])
|
||||
deref_spec.setComponentByName('derefAttr',AttributeDescription(deref_attr))
|
||||
deref_spec.setComponentByName('attributes',deref_attributes)
|
||||
deref_specs.setComponentByPosition(i,deref_spec)
|
||||
i += 1
|
||||
return deref_specs
|
||||
|
||||
def encodeControlValue(self):
|
||||
return encoder.encode(self._derefSpecs())
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=DerefResultControlValue())
|
||||
self.derefRes = {}
|
||||
for deref_res in decodedValue:
|
||||
deref_attr,deref_val,deref_vals = deref_res
|
||||
partial_attrs_dict = dict([
|
||||
(str(t),map(str,v))
|
||||
for t,v in deref_vals or []
|
||||
])
|
||||
try:
|
||||
self.derefRes[str(deref_attr)].append((str(deref_val),partial_attrs_dict))
|
||||
except KeyError:
|
||||
self.derefRes[str(deref_attr)] = [(str(deref_val),partial_attrs_dict)]
|
||||
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[DereferenceControl.controlType] = DereferenceControl
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.openldap - classes for OpenLDAP-specific controls
|
||||
|
||||
See http://www.python-ldap.org/ for project details.
|
||||
|
||||
$Id: openldap.py,v 1.3 2015/06/22 17:56:50 stroeder Exp $
|
||||
$Id: openldap.py,v 1.4 2015/09/18 17:24:39 stroeder Exp $
|
||||
"""
|
||||
|
||||
import ldap.controls
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.readentry - classes for the Read Entry controls
|
||||
(see RFC 4527)
|
||||
|
||||
See http://www.python-ldap.org/ for project details.
|
||||
|
||||
$Id: readentry.py,v 1.4 2011/07/28 08:57:12 stroeder Exp $
|
||||
$Id: readentry.py,v 1.5 2015/09/18 17:24:55 stroeder Exp $
|
||||
"""
|
||||
|
||||
import ldap
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.sessiontrack - class for session tracking control
|
||||
(see draft-wahl-ldap-session)
|
||||
|
||||
See http://www.python-ldap.org/ for project details.
|
||||
|
||||
$Id: sessiontrack.py,v 1.4 2013/07/04 16:20:06 stroeder Exp $
|
||||
$Id: sessiontrack.py,v 1.5 2015/09/18 17:25:07 stroeder Exp $
|
||||
"""
|
||||
|
||||
from ldap.controls import RequestControl
|
||||
|
||||
@@ -3,7 +3,7 @@ ldapobject.py - wraps class _ldap.LDAPObject
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
\$Id: ldapobject.py,v 1.146 2015/06/11 15:13:43 stroeder Exp $
|
||||
\$Id: ldapobject.py,v 1.147 2015/08/08 13:37:41 stroeder Exp $
|
||||
|
||||
Compability:
|
||||
- Tested with Python 2.0+ but should work with Python 1.5.x
|
||||
@@ -847,7 +847,7 @@ class SimpleLDAPObject:
|
||||
if r:
|
||||
return r[0][1]
|
||||
else:
|
||||
raise ldap.NO_SUCH_OBJECT('Empty search result reading entry %s' % (repr(dn)))
|
||||
return None
|
||||
|
||||
def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None):
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,7 @@ ldap.resiter - processing LDAP results with iterators
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
\$Id: resiter.py,v 1.6 2011/07/28 08:23:32 stroeder Exp $
|
||||
\$Id: resiter.py,v 1.7 2015/09/18 20:20:32 stroeder Exp $
|
||||
|
||||
Python compability note:
|
||||
Requires Python 2.3+
|
||||
@@ -15,15 +15,15 @@ class ResultProcessor:
|
||||
Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes.
|
||||
"""
|
||||
|
||||
def allresults(self,msgid,timeout=-1):
|
||||
def allresults(self,msgid,timeout=-1,add_ctrls=0):
|
||||
"""
|
||||
Generator function which returns an iterator for processing all LDAP operation
|
||||
results of the given msgid retrieved with LDAPObject.result3() -> 4-tuple
|
||||
"""
|
||||
result_type,result_list,result_msgid,result_serverctrls = self.result3(msgid,0,timeout)
|
||||
result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls)
|
||||
while result_type and result_list:
|
||||
# Loop over list of search results
|
||||
for result_item in result_list:
|
||||
yield (result_type,result_list,result_msgid,result_serverctrls)
|
||||
result_type,result_list,result_msgid,result_serverctrls = self.result3(msgid,0,timeout)
|
||||
result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls)
|
||||
return # allresults()
|
||||
|
||||
@@ -3,7 +3,7 @@ ldap.schema.subentry - subschema subentry handling
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
\$Id: subentry.py,v 1.35 2015/06/06 09:21:38 stroeder Exp $
|
||||
\$Id: subentry.py,v 1.36 2015/08/08 14:13:30 stroeder Exp $
|
||||
"""
|
||||
|
||||
import ldap.cidict,ldap.schema
|
||||
@@ -481,6 +481,7 @@ def urlfetch(uri,trace_level=0,bytes_mode=None):
|
||||
subschemasubentry_dn,s_temp = ldif_parser.all_records[0]
|
||||
# Work-around for mixed-cased attribute names
|
||||
subschemasubentry_entry = ldap.cidict.cidict()
|
||||
s_temp = s_temp or {}
|
||||
for at,av in s_temp.items():
|
||||
if at in SCHEMA_CLASS_MAPPING:
|
||||
try:
|
||||
|
||||
@@ -3,7 +3,7 @@ ldapurl - handling of LDAP URLs as described in RFC 4516
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
\$Id: ldapurl.py,v 1.72 2015/06/06 09:21:37 stroeder Exp $
|
||||
\$Id: ldapurl.py,v 1.73 2015/08/08 13:36:30 stroeder Exp $
|
||||
|
||||
Python compability note:
|
||||
This module only works with Python 2.0+ since
|
||||
@@ -11,7 +11,7 @@ This module only works with Python 2.0+ since
|
||||
2. list comprehensions are used.
|
||||
"""
|
||||
|
||||
__version__ = '2.4.20'
|
||||
__version__ = '2.4.21'
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
|
||||
@@ -3,13 +3,13 @@ ldif - generate and parse LDIF data (see RFC 2849)
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
$Id: ldif.py,v 1.82 2015/06/21 11:38:32 stroeder Exp $
|
||||
$Id: ldif.py,v 1.83 2015/08/08 13:36:30 stroeder Exp $
|
||||
|
||||
Python compability note:
|
||||
Tested with Python 2.0+, but should work with Python 1.5.2+.
|
||||
"""
|
||||
|
||||
__version__ = '2.4.20'
|
||||
__version__ = '2.4.21'
|
||||
|
||||
__all__ = [
|
||||
# constants
|
||||
|
||||
132
Tests/t_ldif.py
Normal file
132
Tests/t_ldif.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unittest
|
||||
import textwrap
|
||||
|
||||
import ldif
|
||||
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class TestParse(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def check_ldif_to_records(self, ldif_string, expected):
|
||||
#import pdb; pdb.set_trace()
|
||||
got = ldif.ParseLDIF(StringIO(ldif_string))
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
def check_records_to_ldif(self, records, expected):
|
||||
f = StringIO()
|
||||
ldif_writer = ldif.LDIFWriter(f)
|
||||
for dn, attrs in records:
|
||||
ldif_writer.unparse(dn, attrs)
|
||||
got = f.getvalue()
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
def check_roundtrip(self, ldif_source, records, ldif_expected=None):
|
||||
ldif_source = textwrap.dedent(ldif_source).lstrip() + '\n'
|
||||
if ldif_expected is None:
|
||||
ldif_expected = ldif_source
|
||||
else:
|
||||
ldif_expected = textwrap.dedent(ldif_expected).lstrip() + '\n'
|
||||
|
||||
self.check_ldif_to_records(ldif_source, records)
|
||||
self.check_records_to_ldif(records, ldif_expected)
|
||||
self.check_ldif_to_records(ldif_expected, records)
|
||||
|
||||
def test_simple(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
attrib: value
|
||||
attrib: value2
|
||||
""", [
|
||||
('cn=x,cn=y,cn=z', {'attrib': [b'value', b'value2']}),
|
||||
])
|
||||
|
||||
def test_multiple(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
a: v
|
||||
attrib: value
|
||||
attrib: value2
|
||||
|
||||
dn: cn=a,cn=b,cn=c
|
||||
attrib: value2
|
||||
attrib: value3
|
||||
b: v
|
||||
""", [
|
||||
('cn=x,cn=y,cn=z', {'attrib': [b'value', b'value2'], 'a': [b'v']}),
|
||||
('cn=a,cn=b,cn=c', {'attrib': [b'value2', b'value3'], 'b': [b'v']}),
|
||||
])
|
||||
|
||||
def test_folded(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
attrib: very
|
||||
long
|
||||
value
|
||||
attrib2: %s
|
||||
""" % ('asdf.' * 20), [
|
||||
('cn=x,cn=y,cn=z', {'attrib': [b'verylong value'],
|
||||
'attrib2': [b'asdf.' * 20]}),
|
||||
], """
|
||||
dn: cn=x,cn=y,cn=z
|
||||
attrib: verylong value
|
||||
attrib2: asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.as
|
||||
df.asdf.asdf.asdf.asdf.asdf.asdf.
|
||||
""")
|
||||
|
||||
def test_empty(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
attrib:
|
||||
attrib: foo
|
||||
""", [
|
||||
('cn=x,cn=y,cn=z', {'attrib': [b'', b'foo']}),
|
||||
])
|
||||
|
||||
def test_binary(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
attrib:: CQAKOiVA
|
||||
""", [
|
||||
('cn=x,cn=y,cn=z', {'attrib': [b'\t\0\n:%@']}),
|
||||
])
|
||||
|
||||
def test_unicode(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=Michael Stroeder,dc=stroeder,dc=com
|
||||
lastname: Ströder
|
||||
""", [
|
||||
('cn=Michael Stroeder,dc=stroeder,dc=com',
|
||||
{'lastname': [b'Str\303\266der']}),
|
||||
], """
|
||||
dn: cn=Michael Stroeder,dc=stroeder,dc=com
|
||||
lastname:: U3Ryw7ZkZXI=
|
||||
""")
|
||||
|
||||
def test_sorted(self):
|
||||
self.check_roundtrip("""
|
||||
dn: cn=x,cn=y,cn=z
|
||||
b: value_b
|
||||
c: value_c
|
||||
a: value_a
|
||||
""", [
|
||||
('cn=x,cn=y,cn=z', {'a': [b'value_a'],
|
||||
'b': [b'value_b'],
|
||||
'c': [b'value_c']}),
|
||||
], """
|
||||
dn: cn=x,cn=y,cn=z
|
||||
a: value_a
|
||||
b: value_b
|
||||
c: value_c
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
3
setup.py
3
setup.py
@@ -3,7 +3,7 @@ setup.py - Setup package with the help Python's DistUtils
|
||||
|
||||
See http://www.python-ldap.org/ for details.
|
||||
|
||||
$Id: setup.py,v 1.72 2014/03/12 20:29:23 stroeder Exp $
|
||||
$Id: setup.py,v 1.73 2015/09/19 13:38:30 stroeder Exp $
|
||||
"""
|
||||
|
||||
import sys,os,string,time
|
||||
@@ -164,6 +164,7 @@ setup(
|
||||
'ldap.async',
|
||||
'ldap.compat',
|
||||
'ldap.controls',
|
||||
'ldap.controls.deref',
|
||||
'ldap.controls.libldap',
|
||||
'ldap.controls.openldap',
|
||||
'ldap.controls.ppolicy',
|
||||
|
||||
Reference in New Issue
Block a user