Search code examples
ldapasn.1python-ldap

Create a "sequence of sequence" ldap.controls.RequestControl object


I use python-ldap to search an ActiveDirectory as if it was any regular LDAP server. It works, SSL, authentication and everything.

Now I want to use the server side sort request control defined in RFC 2891, which has the OID 1.2.840.113556.1.4.473.

Python-ldap does not support that control out of the box, so I must create it myself. I did this :

server_side_sort = ldap.controls.RequestControl('1.2.840.113556.1.4.473', True) 

But I don't know how to compute the encodedControlValue parameter, which is the BER-encoded ASN.1 control value.

I see that pyasn1 has many parts to comptue it, like univ.SequenceOf, univ.Sequence and univ.Boolean. Looking at the RFC2251 module of pyasn1, I came up with this :

class LDAPString(univ.OctetString): pass

class AttributeDescription(LDAPString): pass

class MatchingRuleId(LDAPString): pass

class ServerSideSortOnName(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('attributeDesc', AttributeDescription('name')), 
        namedtype.NamedType('reverseOrder', univ.Boolean(False)) )

class ServerSideSortControl(univ.SequenceOf):
     componentType = ServerSideSortOnName()

sss = ServerSideSortControl()

serversidesort = ldap.controls.RequestControl('1.2.840.113556.1.4.473',True,sss)

Name might not be indexed, I know. I thought it could help debugging to match the base64 value shown on this MSDN snippet.

But when I add this control (which might not even be valid) to ldap.search_ext, I get the error TypeError: ('expected a string', ServerSideSortControl())

How can I create a server side sort control value on attribute name that python-ldap ldap.search_ext will accept, using pyasn1 or similar ?


Solution

  • You have to nest univ.Sequence in univ.SequenceOf and implement encodeControlValue that returns the BER encoded control the server expect.

    class SSSRequest(univ.SequenceOf):
        componentType = univ.Sequence()
    
    class SSSRequestSequence(univ.Sequence):
        componentType = namedtype.NamedTypes(
            namedtype.NamedType('attributeType', univ.OctetString()),
        )
    
    class SSS_CONTROL_REQUEST(LDAPControl):
        def __init__(self,controlType,criticality,controlValue=None,encodedControlValue=None):
            LDAPControl.__init__(self,controlType,criticality,controlValue,encodedControlValue)
    
        def encodeControlValue(self):
            sss = SSSRequest()
    
            for k in self.controlValue:
                Skey = SSSRequestSequence()
                Skey.setComponentByName('attributeType', k)
                sss.setComponentByPosition(0, Skey)
            return encoder.encode(sss)
    
        def decodeControlValue(self,encodedValue):
            sssr = decoder.decode(encodedValue)[0]
            rsp = SSSResponse()
            for n, v in enumerate(sssr):
                try:
                    rsp.setComponentByPosition(n, int(v))
                except Exception, e:
                    print str(e)
            if rsp.success:
                return True
            return rsp.error
    
    if __name__ == '__main__':
        SSSREQUEST_OID = '1.2.840.113556.1.4.473'
        sss = SSS_CONTROL_REQUEST(SSSREQUEST_OID, False, ['cn'])
        srv = ldap.open('localhost')
        srv.simple_bind_s()
        id = srv.search_ext('ou=people,dc=example,dc=com', ldap.SCOPE_SUBTREE, filterstr='(objectClass=user)', serverctrls=[sss])
        print srv.result3(id)
    

    Implementing reverse order sort is left as an exercice ;)

    The code was tested successfully with a AD-LDS instance running on Windows Server 2008 R2, 64 bits (but you have to make the bind non-anonymous).