From a4169eb36ed3240e54a52b9900ed14a46c70c802 Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 22 Jun 2015 16:47:08 +0000 Subject: [PATCH] Added modules ldap.controls.vlv and ldap.controls.sss for Virtual List View (see draft-ietf-ldapext-ldapv3-vlv) and Server-side Sorting (see RFC 2891) --- CHANGES | 5 +- Lib/ldap/controls/sss.py | 131 +++++++++++++++++++++++++++++++++++++ Lib/ldap/controls/vlv.py | 136 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 Lib/ldap/controls/sss.py create mode 100644 Lib/ldap/controls/vlv.py diff --git a/CHANGES b/CHANGES index 7d2886c..fe4f23f 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,9 @@ Lib/ - Removed non-existent 'AttrTypeandValueLDIF' from ldif.__all__ * New mix-in class ldap.controls.openldap.SearchNoOpMixIn adds convience method noop_search_st() to LDAPObject class +* Added new experimental modules which implement the control classes + for Virtual List View (see draft-ietf-ldapext-ldapv3-vlv) and + Server-side Sorting (see RFC 2891) (thanks to Benjamin Dauvergne) ---------------------------------------------------------------- Released 2.4.19 2015-01-10 @@ -1170,4 +1173,4 @@ Released 2.0.0pre02 2002-02-01 ---------------------------------------------------------------- Released 1.10alpha3 2000-09-19 -$Id: CHANGES,v 1.346 2015/06/22 11:51:07 stroeder Exp $ +$Id: CHANGES,v 1.347 2015/06/22 16:47:08 stroeder Exp $ diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py new file mode 100644 index 0000000..406fb0b --- /dev/null +++ b/Lib/ldap/controls/sss.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +ldap.controls.sss - classes for Server Side Sorting +(see RFC 2891) + +See http://www.python-ldap.org/ for project details. + +$Id: sss.py,v 1.1 2015/06/22 16:47:08 stroeder Exp $ +""" + +__all__ = [ + 'SSSRequestControl', + 'SSSResponseControl', + 'SSSVLVPagedLDAPObject' +] + + +import ldap +from ldap.ldapobject import LDAPObject +from ldap.controls import (RequestControl, ResponseControl, + KNOWN_RESPONSE_CONTROLS, DecodeControlTuples) + +from pyasn1.type import univ, namedtype, tag, namedval, constraint +from pyasn1.codec.ber import encoder, decoder + + +# SortKeyList ::= SEQUENCE OF SEQUENCE { +# attributeType AttributeDescription, +# orderingRule [0] MatchingRuleId OPTIONAL, +# reverseOrder [1] BOOLEAN DEFAULT FALSE } + + +class SortKeyType(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('attributeType', univ.OctetString()), + namedtype.OptionalNamedType('orderingRule', + univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0) + ) + ), + namedtype.DefaultedNamedType('reverseOrder', univ.Boolean(False).subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))) + + +class SortKeyListType(univ.SequenceOf): + componentType = SortKeyType() + + +class SSSRequestControl(RequestControl): + '''Order result server side + + >>> s = SSSRequestControl('-cn') + ''' + controlType = '1.2.840.113556.1.4.473' + + def __init__( + self, + criticality=False, + ordering_rules=None, + ): + RequestControl.__init__(self,self.controlType,criticality) + self.ordering_rules = ordering_rules + if isinstance(ordering_rules, basestring): + ordering_rules = [ordering_rules] + for rule in ordering_rules: + rule = rule.split(':') + assert len(rule) < 3, 'syntax for ordering rule: [-][:ordering-rule]' + + def asn1(self): + p = SortKeyListType() + for i, rule in enumerate(self.ordering_rules): + q = SortKeyType() + reverse_order = rule.startswith('-') + if reverse_order: + rule = rule[1:] + if ':' in rule: + attribute_type, ordering_rule = rule.split(':') + else: + attribute_type, ordering_rule = rule, None + q.setComponentByName('attributeType', attribute_type) + if ordering_rule: + q.setComponentByName('orderingRule', ordering_rule) + if reverse_order: + q.setComponentByName('reverseOrder', 1) + p.setComponentByPosition(i, q) + return p + + def encodeControlValue(self): + return encoder.encode(self.asn1()) + + +class SortResultType(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('sortResult', univ.Enumerated().subtype( + namedValues=namedval.NamedValues( + ('success', 0), + ('operationsError', 1), + ('timeLimitExceeded', 3), + ('strongAuthRequired', 8), + ('adminLimitExceeded', 11), + ('noSuchAttribute', 16), + ('inappropriateMatching', 18), + ('insufficientAccessRights', 50), + ('busy', 51), + ('unwillingToPerform', 53), + ('other', 80)), + subtypeSpec=univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint( + 0, 1, 3, 8, 11, 16, 18, 50, 51, 53, 80))), + namedtype.OptionalNamedType('attributeType', + univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0) + ) + )) + + +class SSSResponseControl(ResponseControl): + controlType = '1.2.840.113556.1.4.474' + + def __init__(self,criticality=False): + ResponseControl.__init__(self,self.controlType,criticality) + + def decodeControlValue(self, encoded): + p, rest = decoder.decode(encoded, asn1Spec=SortResultType()) + assert not rest, 'all data could not be decoded' + self.result = int(p.getComponentByName('sortResult')) + self.result_code = p.getComponentByName('sortResult').prettyOut(self.result) + self.attribute_type_error = p.getComponentByName('attributeType') + + +KNOWN_RESPONSE_CONTROLS[SSSRequestControl.controlType] = SSSRequestControl +KNOWN_RESPONSE_CONTROLS[SSSResponseControl.controlType] = SSSResponseControl diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py new file mode 100644 index 0000000..b439cdd --- /dev/null +++ b/Lib/ldap/controls/vlv.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +ldap.controls.vlv - classes for Simple Paged control +(see draft-ietf-ldapext-ldapv3-vlv) + +See http://www.python-ldap.org/ for project details. + +$Id: vlv.py,v 1.1 2015/06/22 16:47:08 stroeder Exp $ +""" + +__all__ = [ + 'VLVRequestControl', + 'VLVResponseControl', +] + +import ldap +from ldap.ldapobject import LDAPObject +from ldap.controls import (RequestControl, ResponseControl, + KNOWN_RESPONSE_CONTROLS, DecodeControlTuples) + +from pyasn1.type import univ, namedtype, tag, namedval, constraint +from pyasn1.codec.ber import encoder, decoder + + +class ByOffsetType(univ.Sequence): + tagSet = univ.Sequence.tagSet.tagImplicitly( + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)) + componentType = namedtype.NamedTypes( + namedtype.NamedType('offset', univ.Integer()), + namedtype.NamedType('contentCount', univ.Integer())) + + +class TargetType(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('byOffset', ByOffsetType()), + namedtype.NamedType('greaterThanOrEqual', univ.OctetString().subtype( + implicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatSimple, 1)))) + + +class VirtualListViewRequestType(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('beforeCount', univ.Integer()), + namedtype.NamedType('afterCount', univ.Integer()), + namedtype.NamedType('target', TargetType()), + namedtype.OptionalNamedType('contextID', univ.OctetString())) + +class VLVRequestControl(RequestControl): + controlType = '2.16.840.1.113730.3.4.9' + + def __init__( + self, + criticality=False, + before_count=0, + after_count=0, + offset=None, + content_count=None, + greater_than_or_equal=None, + context_id=None, + ): + RequestControl.__init__(self,self.controlType,criticality) + assert (offset is not None and content_count is not None) or greater_than_or_equal, 'offset and ' \ + 'content_count must be set together or greater_than_or_equal must be ' \ + 'used' + self.before_count = before_count + self.after_count = after_count + self.offset = offset + self.content_count = content_count + self.greater_than_or_equal = greater_than_or_equal + self.context_id = context_id + + def encodeControlValue(self): + p = VirtualListViewRequestType() + p.setComponentByName('beforeCount', self.before_count) + p.setComponentByName('afterCount', self.after_count) + if self.offset is not None and self.content_count is not None: + by_offset = ByOffsetType() + by_offset.setComponentByName('offset', self.offset) + by_offset.setComponentByName('contentCount', self.content_count) + target = TargetType() + target.setComponentByName('byOffset', by_offset) + elif self.greater_than_or_equal: + target = TargetType() + target.setComponentByName('greaterThanOrEqual', + self.greater_than_or_equal) + else: + raise NotImplementedError + p.setComponentByName('target', target) + return encoder.encode(p) + +KNOWN_RESPONSE_CONTROLS[VLVRequestControl.controlType] = VLVRequestControl + + +class VirtualListViewResultType(univ.Enumerated): + namedValues = namedval.NamedValues( + ('success', 0), + ('operationsError', 1), + ('protocolError', 3), + ('unwillingToPerform', 53), + ('insufficientAccessRights', 50), + ('adminLimitExceeded', 11), + ('innapropriateMatching', 18), + ('sortControlMissing', 60), + ('offsetRangeError', 61), + ('other', 80), + ) + + +class VirtualListViewResponseType(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('targetPosition', univ.Integer()), + namedtype.NamedType('contentCount', univ.Integer()), + namedtype.NamedType('virtualListViewResult', + VirtualListViewResultType()), + namedtype.OptionalNamedType('contextID', univ.OctetString())) + + +class VLVResponseControl(ResponseControl): + controlType = '2.16.840.1.113730.3.4.10' + + def __init__(self,criticality=False): + ResponseControl.__init__(self,self.controlType,criticality) + + def decodeControlValue(self,encoded): + p, rest = decoder.decode(encoded, asn1Spec=VirtualListViewResponseType()) + assert not rest, 'all data could not be decoded' + self.target_position = int(p.getComponentByName('targetPosition')) + self.content_count = int(p.getComponentByName('contentCount')) + self.result = int(p.getComponentByName('virtualListViewResult')) + self.result_code = p.getComponentByName('virtualListViewResult') \ + .prettyOut(self.result) + self.context_id = p.getComponentByName('contextID') + if self.context_id: + self.context_id = str(self.context_id) + +KNOWN_RESPONSE_CONTROLS[VLVResponseControl.controlType] = VLVResponseControl