> In relation to ticket 47757, I have started work on a deref control for > Noriko. > The idea is to get it working in lib389, then get it upstreamed into pyldap. > > At this point it's all done, except that the actual request control doesn't > appear to work. Could one of the lib389 / ldap python experts cast their eye > over this and let me know where I've gone wrong? I have improved this, but am having issues with the asn1spec for ber decoding. I have attached the updated patch, but specifically the issue is in _controls.py I would appreciate if anyone could take a look at this, and let me know if there is something I have missed. """ controlValue ::= SEQUENCE OF derefRes DerefRes DerefRes ::= SEQUENCE { derefAttr AttributeDescription, derefVal LDAPDN, attrVals [0] PartialAttributeList OPTIONAL } PartialAttributeList ::= SEQUENCE OF partialAttribute PartialAttribute """ 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() .... def decodeControlValue(self,encodedControlValue): self.entry = {} #decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=DerefResultControlValue()) # Gets the error: TagSet(Tag(tagClass=0, tagFormat=32, tagId=16), Tag(tagClass=128, tagFormat=32, tagId=0)) not in asn1Spec: {TagSet(Tag(tagClass=0, tagFormat=32, tagId=16)): PartialAttributeList()}/{} decodedValue,_ = decoder.decode(encodedControlValue) print(decodedValue.prettyPrint()) # Pretty print yields #Sequence: <-- Sequence of # <no-name>=Sequence: <-- derefRes # <no-name>=uniqueMember <-- derefAttr # <no-name>=uid=test,dc=example,dc=com <-- derefVal # <no-name>=Sequence: <-- attrVals # <no-name>=uid # <no-name>=Set: # <no-name>=test # For now, while asn1spec is sad, we'll just rely on it being well formed # However, this isn't good, as without the asn1spec, we seem to actually be dropping values .... for result in decodedValue: derefAttr, derefVal, _ = result self.entry[str(derefAttr)] = str(derefVal)
From 22f1912f95e2188743525dba876cd122db27bb13 Mon Sep 17 00:00:00 2001 From: William Brown <william@xxxxxxxxxxxxxxxx> Date: Tue, 25 Aug 2015 15:38:11 +0930 Subject: [PATCH] Add dereference request control to lib389 for testing. --- lib389/__init__.py | 43 ++++++++++++++++++ lib389/_constants.py | 6 +++ lib389/_controls.py | 110 ++++++++++++++++++++++++++++++++++++++++++++++ tests/dereference_test.py | 81 ++++++++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 lib389/_controls.py create mode 100644 tests/dereference_test.py diff --git a/lib389/__init__.py b/lib389/__init__.py index 8dfe28f..201bf32 100644 --- a/lib389/__init__.py +++ b/lib389/__init__.py @@ -73,6 +73,7 @@ MAJOR, MINOR, _, _, _ = sys.version_info if MAJOR >= 3 or (MAJOR == 2 and MINOR >= 7): from ldap.controls.simple import GetEffectiveRightsControl + from lib389._controls import DereferenceControl RE_DBMONATTR = re.compile(r'^([a-zA-Z]+)-([1-9][0-9]*)$') RE_DBMONATTRSUN = re.compile(r'^([a-zA-Z]+)-([a-zA-Z]+)$') @@ -2401,6 +2402,48 @@ class DirSrv(SimpleLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, []) return ldap_result + # Is there a better name for this function? + def dereference(self, deref, base=DEFAULT_SUFFIX, scope=ldap.SCOPE_SUBTREE, *args, **kwargs): + """ + Perform a search which dereferences values from attributes such as member + or unique member. + For arguments to this function, please see LDAPObject.search_s. For example: + + @param deref - Dereference query + @param base - Base DN of the suffix to check + @param scope - search scope + @param args - + @param kwargs - + @return - ldap result + + LDAPObject.search_s(base, scope[, filterstr='(objectClass=*)'[, attrlist=None[, attrsonly=0]]]) -> list|None + + A deref query is of the format: + + "<attribute to derference>:<deref attr1>,<deref attr2>..." + + "uniqueMember:dn,objectClass" + + This will return the dn's and objectClasses of the dereferenced members of the group. + """ + if not (MAJOR >= 3 or (MAJOR == 2 and MINOR >= 7)): + raise Exception("UNSUPPORTED EXTENDED OPERATION ON THIS VERSION OF PYTHON") + ldap_result = None + # This may not be thread safe. Is there a better way to do this? + try: + drc = DereferenceControl(True, deref=deref.encode('UTF-8')) + sctrl = [drc] + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + + #ldap_result = self.search_s(base, scope, *args, **kwargs) + res = self.search(base, scope, *args, **kwargs) + resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value = self.result4(res, add_ctrls=1, resp_ctrl_classes={CONTROL_DEREF: DereferenceControl}) + print(resp_name) + print(resp_value) + finally: + self.set_option(ldap.OPT_SERVER_CONTROLS, []) + return resp_data, decoded_resp_ctrls + def buildLDIF(self, num, ldif_file, suffix='dc=example,dc=com'): """Generate a simple ldif file using the dbgen.pl script, and set the ownership and permissions to match the user that the server runs as. diff --git a/lib389/_constants.py b/lib389/_constants.py index efabfc6..69c305b 100644 --- a/lib389/_constants.py +++ b/lib389/_constants.py @@ -114,6 +114,12 @@ RDN_REPLICA = "cn=replica" RETROCL_SUFFIX = "cn=changelog" +################################## +### +### Request Control OIDS +### +################################## +CONTROL_DEREF = '1.3.6.1.4.1.4203.666.5.16' ################################## ### diff --git a/lib389/_controls.py b/lib389/_controls.py new file mode 100644 index 0000000..93c5fe0 --- /dev/null +++ b/lib389/_controls.py @@ -0,0 +1,110 @@ +""" +Lib389 python ldap request controls. + +These should be upstreamed into python ldap when possible. +""" + +from lib389._constants import * + +from ldap.controls import LDAPControl + +from pyasn1.type import namedtype,univ +from pyasn1.codec.ber import encoder,decoder +from pyasn1_modules.rfc2251 import AttributeDescription, LDAPDN, PartialAttributeList + +# Could use AttributeDescriptionList + +""" + controlValue ::= SEQUENCE OF derefSpec DerefSpec + + DerefSpec ::= SEQUENCE { + derefAttr attributeDescription, ; with DN syntax + attributes AttributeList } + + AttributeList ::= SEQUENCE OF attr AttributeDescription + + Needs to be matched by ber_scanf(ber, "{a{v}}", ... ) +""" +class AttributeList(univ.SequenceOf): + componentType = AttributeDescription() + +class DerefSpec(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('derefAttr', AttributeDescription()), + namedtype.NamedType('attributes', AttributeList()), + ) + +class DerefControlValue(univ.SequenceOf): + componentType = DerefSpec() + +""" + controlValue ::= SEQUENCE OF derefRes DerefRes + + DerefRes ::= SEQUENCE { + derefAttr AttributeDescription, + derefVal LDAPDN, + attrVals [0] PartialAttributeList OPTIONAL } + + PartialAttributeList ::= SEQUENCE OF + partialAttribute PartialAttribute +""" + +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): + """ + Dereference Control + """ + + def __init__(self, criticality=True, deref=None): + LDAPControl.__init__(self, CONTROL_DEREF, criticality) + self.deref = deref + + def encodeControlValue(self): + cv = DerefControlValue() + cvi = 0 + for derefSpec in self.deref.split(';'): + derefAttr, attributes = derefSpec.split(':') + attributes = attributes.split(',') + al = AttributeList() + i = 0 + while len(attributes) > 0: + al.setComponentByPosition(i, attributes.pop()) + i += 1 + ds = DerefSpec() + ds.setComponentByName('derefAttr', derefAttr) + ds.setComponentByName('attributes', al) + cv.setComponentByPosition(cvi, ds) + cvi += 1 + return encoder.encode(cv) + + def decodeControlValue(self,encodedControlValue): + self.entry = {} + #decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=DerefResultControlValue()) + # Gets the error: TagSet(Tag(tagClass=0, tagFormat=32, tagId=16), Tag(tagClass=128, tagFormat=32, tagId=0)) not in asn1Spec: {TagSet(Tag(tagClass=0, tagFormat=32, tagId=16)): PartialAttributeList()}/{} + decodedValue,_ = decoder.decode(encodedControlValue) + print(decodedValue.prettyPrint()) + # Pretty print yields + #Sequence: <-- Sequence of + # <no-name>=Sequence: <-- derefRes + # <no-name>=uniqueMember <-- derefAttr + # <no-name>=uid=test,dc=example,dc=com <-- derefVal + # <no-name>=Sequence: <-- attrVals + # <no-name>=uid + # <no-name>=Set: + # <no-name>=test + # For now, while asn1spec is sad, we'll just rely on it being well formed + # However, this isn't good, as without the asn1spec, we seem to actually be dropping values .... + for result in decodedValue: + derefAttr, derefVal, _ = result + self.entry[str(derefAttr)] = str(derefVal) + return self.entry + diff --git a/tests/dereference_test.py b/tests/dereference_test.py new file mode 100644 index 0000000..86c2b31 --- /dev/null +++ b/tests/dereference_test.py @@ -0,0 +1,81 @@ +''' +Created on Aug 1, 2015 + +@author: William Brown +''' +from lib389._constants import * +from lib389 import DirSrv,Entry +import ldap + +INSTANCE_PORT = 54321 +INSTANCE_SERVERID = 'dereferenceds' +INSTANCE_PREFIX = '/srv' + +class Test_dereference(): + def setUp(self): + instance = DirSrv(verbose=False) + instance.log.debug("Instance allocated") + args = {SER_HOST: LOCALHOST, + SER_PORT: INSTANCE_PORT, + SER_DEPLOYED_DIR: INSTANCE_PREFIX, + SER_SERVERID_PROP: INSTANCE_SERVERID + } + instance.allocate(args) + if instance.exists(): + instance.delete() + instance.create() + instance.open() + self.instance = instance + + def tearDown(self): + if self.instance.exists(): + #self.instance.db2ldif(bename='userRoot', suffixes=[DEFAULT_SUFFIX], excludeSuffixes=[], encrypt=False, \ + #repl_data=False, outputfile='%s/ldif/%s.ldif' % (self.instance.dbdir,INSTANCE_SERVERID )) + #self.instance.clearBackupFS() + #self.instance.backupFS() + self.instance.delete() + + def add_user(self): + # Create a user entry + for i in range(0,2): + uentry = Entry('uid=test%s,%s' % (i, DEFAULT_SUFFIX)) + uentry.setValues('objectclass', 'top', 'extensibleobject') + uentry.setValues('uid', 'test') + self.instance.add_s(uentry) + #self.instance.log.debug("Created user entry as:" ,uentry.dn) + + def add_group(self): + # Create a group for the user to have some rights to + gentry = Entry('cn=testgroup,%s' % DEFAULT_SUFFIX) + gentry.setValues('objectclass', 'top', 'extensibleobject') + gentry.setValues('cn', 'testgroup') + for i in range(0,2): + gentry.setValues('uniqueMember', 'uid=test%s,%s' % (i,DEFAULT_SUFFIX)) + self.instance.add_s(gentry) + + def test_dereference(self): + # Run an effective rights search + try: + result, control_response = self.instance.dereference('uniqueMember:dn,uid;uniqueMember:dn,uid', filterstr='(cn=testgroup)') + assert False + except ldap.UNAVAILABLE_CRITICAL_EXTENSION: + # This is a good thing! It means our deref Control Value is correctly formatted. + pass + result, control_response = self.instance.dereference('uniqueMember:cn,uid,objectclass', filterstr='(cn=testgroup)') + + #rights = result[0] + print(result[0]) + print(control_response) + # This isn't meant to be in the result, it should be in control response + assert result[0][2][0].entry == {'uniqueMember': 'uid=test1,dc=example,dc=com'} + #assert rights.getValue('attributeLevelRights') == 'cn:rsc' + #assert rights.getValue('entryLevelRights') == 'v' + +if __name__ == "__main__": + test = Test_dereference() + test.setUp() + test.add_user() + test.add_group() + test.test_dereference() + test.tearDown() + -- 2.4.3
-- 389-devel mailing list 389-devel@xxxxxxxxxxxxxxxxxxxxxxx https://admin.fedoraproject.org/mailman/listinfo/389-devel