class BaseLDAPServer(protocol.Protocol): debug = False def __init__(self): self.buffer = '' self.connected = None berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()))) def dataReceived(self, recd): self.buffer += recd while 1: try: o, bytes = pureber.berDecodeObject(self.berdecoder, self.buffer) except pureber.BERExceptionInsufficientData: o, bytes = None, 0 self.buffer = self.buffer[bytes:] if o is None: break self.handle(o) def connectionMade(self): """TCP connection has opened""" self.connected = 1 def connectionLost(self, reason=protocol.connectionDone): """Called when TCP connection has been lost""" self.connected = 0 def queue(self, id, op): if not self.connected: raise LDAPServerConnectionLostException() msg = pureldap.LDAPMessage(op, id=id) if self.debug: log.msg('S->C %s' % repr(msg), debug=True) self.transport.write(str(msg)) def unsolicitedNotification(self, msg): log.msg("Got unsolicited notification: %s" % repr(msg)) def checkControls(self, controls): if controls is not None: for controlType, criticality, controlValue in controls: if criticality: raise ldaperrors.LDAPUnavailableCriticalExtension( 'Unknown control %s' % controlType) def handleUnknown(self, request, controls, callback): log.msg('Unknown request: %r' % request) msg = pureldap.LDAPExtendedResponse( resultCode=ldaperrors.LDAPProtocolError.resultCode, responseName='1.3.6.1.4.1.1466.20036', errorMessage='Unknown request') return msg def _cbLDAPError(self, reason, name): reason.trap(ldaperrors.LDAPException) return self._callErrorHandler(name=name, resultCode=reason.value.resultCode, errorMessage=reason.value.message) def _cbHandle(self, response, id): if response is not None: self.queue(id, response) def failDefault(self, resultCode, errorMessage): return pureldap.LDAPExtendedResponse( resultCode=resultCode, responseName='1.3.6.1.4.1.1466.20036', errorMessage=errorMessage) def _callErrorHandler(self, name, resultCode, errorMessage): errh = getattr(self, 'fail_' + name, self.failDefault) return errh(resultCode=resultCode, errorMessage=errorMessage) def _cbOtherError(self, reason, name): return self._callErrorHandler( name=name, resultCode=ldaperrors.LDAPProtocolError.resultCode, errorMessage=reason.getErrorMessage()) def handle(self, msg): assert isinstance(msg.value, pureldap.LDAPProtocolRequest) if self.debug: log.msg('S<-C %s' % repr(msg), debug=True) if msg.id == 0: self.unsolicitedNotification(msg.value) else: name = msg.value.__class__.__name__ handler = getattr(self, 'handle_' + name, self.handleUnknown) d = defer.maybeDeferred( handler, msg.value, msg.controls, lambda response: self._cbHandle(response, msg.id)) d.addErrback(self._cbLDAPError, name) d.addErrback(defer.logError) d.addErrback(self._cbOtherError, name) d.addCallback(self._cbHandle, msg.id)
class KnownValues(unittest.TestCase): knownValues=( # class, args, kwargs, expected_result (pureldap.LDAPModifyRequest, [], { "object": 'cn=foo, dc=example, dc=com', "modification": [ pureber.BERSequence([ pureber.BEREnumerated(0), pureber.BERSequence([ pureldap.LDAPAttributeDescription('bar'), pureber.BERSet([ pureldap.LDAPString('a'), pureldap.LDAPString('b'), ]), ]), ]), ], }, None, [0x66, 50] + ([0x04, 0x1a] + l(b"cn=foo, dc=example, dc=com") + [0x30, 20] + ([0x30, 18] + ([0x0a, 0x01, 0x00] + [0x30, 13] + ([0x04, len(b"bar")] + l(b"bar") + [0x31, 0x06] + ([0x04, len(b"a")] + l(b"a") + [0x04, len(b"b")] + l(b"b")))))) ), (pureldap.LDAPModifyRequest, [], { "object": 'cn=foo, dc=example, dc=com', "modification": [ pureber.BERSequence([ pureber.BEREnumerated(1), pureber.BERSequence([ pureber.BEROctetString('bar'), pureber.BERSet([]), ]), ]), ], }, None, [0x66, 0x2c] + ([0x04, 0x1a] + l(b"cn=foo, dc=example, dc=com") + [0x30, 0x0e] + ([0x30, 0x0c] + ([0x0a, 0x01, 0x01] + [0x30, 0x07] + ([0x04, 0x03] + l(b"bar") + [0x31, 0x00])))) ), (pureldap.LDAPFilter_not, [], { "value": pureldap.LDAPFilter_present("foo"), }, pureldap.LDAPBERDecoderContext_Filter(fallback=pureber.BERDecoderContext()), [0xa2, 0x05] + [0x87] + [len(b"foo")] + l(b"foo")), (pureldap.LDAPFilter_or, [], { "value": [pureldap.LDAPFilter_equalityMatch( attributeDesc=pureldap.LDAPAttributeDescription(value='cn'), assertionValue=pureldap.LDAPAssertionValue(value='foo')), pureldap.LDAPFilter_equalityMatch( attributeDesc=pureldap.LDAPAttributeDescription(value='uid'), assertionValue=pureldap.LDAPAssertionValue(value='foo')), ] }, pureldap.LDAPBERDecoderContext_Filter(fallback=pureber.BERDecoderContext()), [0xa1, 23] + [0xa3, 9] + [0x04] + [len(b"cn")] + l(b"cn") + [0x04] + [len(b"foo")] + l(b"foo") + [0xa3, 10] + [0x04] + [len(b"uid")] + l(b"uid") + [0x04] + [len(b"foo")] + l(b"foo"), ), (pureldap.LDAPFilter_and, [], { "value": [pureldap.LDAPFilter_equalityMatch( attributeDesc=pureldap.LDAPAttributeDescription(value='cn'), assertionValue=pureldap.LDAPAssertionValue(value='foo')), pureldap.LDAPFilter_equalityMatch( attributeDesc=pureldap.LDAPAttributeDescription(value='uid'), assertionValue=pureldap.LDAPAssertionValue(value='foo')), ] }, pureldap.LDAPBERDecoderContext_Filter(fallback=pureber.BERDecoderContext()), [0xa0, 23] + [0xa3, 9] + [0x04] + [len(b"cn")] + l(b"cn") + [0x04] + [len(b"foo")] + l(b"foo") + [0xa3, 10] + [0x04] + [len(b"uid")] + l(b"uid") + [0x04] + [len(b"foo")] + l(b"foo"), ), (pureldap.LDAPModifyDNRequest, [], {'entry': 'cn=foo,dc=example,dc=com', 'newrdn': 'uid=bar', 'deleteoldrdn': 0, }, None, [0x6c, 0x26] + [0x04] + [len(b"cn=foo,dc=example,dc=com")] + l(b"cn=foo,dc=example,dc=com") + [0x04] + [len(b"uid=bar")] + l(b"uid=bar") + [0x01, 0x01, 0x00]), (pureldap.LDAPModifyDNRequest, [], {'entry': 'cn=aoue,dc=example,dc=com', 'newrdn': 'uid=aoue', 'deleteoldrdn': 0, 'newSuperior': 'ou=People,dc=example,dc=com', }, None, [0x6c, 69] + [0x04] + [len(b"cn=aoue,dc=example,dc=com")] + l(b"cn=aoue,dc=example,dc=com") + [0x04] + [len(b"uid=aoue")] + l(b"uid=aoue") + [0x01, 0x01, 0x00] + [0x80] + [len(b"ou=People,dc=example,dc=com")] + l(b"ou=People,dc=example,dc=com")), (pureldap.LDAPSearchRequest, [], {'baseObject': 'dc=yoja,dc=example,dc=com', }, None, [0x63, 57] + [0x04] + [len(b'dc=yoja,dc=example,dc=com')] + l(b'dc=yoja,dc=example,dc=com') # scope + [0x0a, 1, 2] # derefAliases + [0x0a, 1, 0] # sizeLimit + [0x02, 1, 0] # timeLimit + [0x02, 1, 0] # typesOnly + [0x01, 1, 0] # filter + [135, 11] + l(b'objectClass') # attributes + [48, 0] ), (pureldap.LDAPUnbindRequest, [], {}, None, [0x42, 0x00] ), (pureldap.LDAPSearchResultDone, [], {'resultCode': 0, }, None, [0x65, 0x07] # resultCode + [0x0a, 0x01, 0x00] # matchedDN + [0x04] + [len(b'')] + l(b'') # errorMessage + [0x04] + [len(b'')] + l(b'') # referral, TODO + [] ), (pureldap.LDAPSearchResultDone, [], {'resultCode': 0, 'matchedDN': 'dc=foo,dc=example,dc=com', }, None, [0x65, 31] # resultCode + [0x0a, 0x01, 0x00] # matchedDN + [0x04] + [len(b'dc=foo,dc=example,dc=com')] + l(b'dc=foo,dc=example,dc=com') # errorMessage + [0x04] + [len(b'')] + l(b'') # referral, TODO + [] ), (pureldap.LDAPSearchResultDone, [], {'resultCode': 0, 'matchedDN': 'dc=foo,dc=example,dc=com', 'errorMessage': 'the foobar was fubar', }, None, [0x65, 51] # resultCode + [0x0a, 0x01, 0x00] # matchedDN + [0x04] + [len(b'dc=foo,dc=example,dc=com')] + l(b'dc=foo,dc=example,dc=com') # errorMessage + [0x04] + [len(b'the foobar was fubar')] + l(b'the foobar was fubar',) # referral, TODO + [] ), (pureldap.LDAPSearchResultDone, [], {'resultCode': 0, 'errorMessage': 'the foobar was fubar', }, None, [0x65, 27] # resultCode + [0x0a, 0x01, 0x00] # matchedDN + [0x04] + [len(b'')] + l(b'') # errorMessage + [0x04] + [len(b'the foobar was fubar')] + l(b'the foobar was fubar',) # referral, TODO + [] ), (pureldap.LDAPMessage, [], {'id': 42, 'value': pureldap.LDAPBindRequest(), }, pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))), [0x30, 12] # id + [0x02, 0x01, 42] # value + l(pureldap.LDAPBindRequest().toWire()) ), (pureldap.LDAPControl, [], {'controlType': '1.2.3.4', }, None, [0x30, 9] # controlType + [0x04, 7] + l(b"1.2.3.4") ), (pureldap.LDAPControl, [], {'controlType': '1.2.3.4', 'criticality': True, }, None, [0x30, 12] # controlType + [0x04, 7] + l(b"1.2.3.4") # criticality + [0x01, 1, 0xFF] ), (pureldap.LDAPControl, [], {'controlType': '1.2.3.4', 'criticality': True, 'controlValue': 'silly', }, None, [0x30, 19] # controlType + [0x04, 7] + l(b"1.2.3.4") # criticality + [0x01, 1, 0xFF] # controlValue + [0x04, len(b"silly")] + l(b"silly") ), (pureldap.LDAPMessage, [], {'id': 42, 'value': pureldap.LDAPBindRequest(), 'controls': [ ('1.2.3.4', None, None), ('2.3.4.5', False), ('3.4.5.6', True, b'\x00\x01\x02\xFF'), ('4.5.6.7', None, b'\x00\x01\x02\xFF'), ], }, pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))), [0x30, 76] # id + [0x02, 0x01, 42] # value + l(pureldap.LDAPBindRequest().toWire()) # controls + l(pureldap.LDAPControls(value=[ pureldap.LDAPControl(controlType='1.2.3.4'), pureldap.LDAPControl(controlType='2.3.4.5', criticality=False), pureldap.LDAPControl(controlType='3.4.5.6', criticality=True, controlValue=b'\x00\x01\x02\xFF'), pureldap.LDAPControl(controlType='4.5.6.7', criticality=None, controlValue=b'\x00\x01\x02\xFF'), ]).toWire()), ), (pureldap.LDAPFilter_equalityMatch, [], {'attributeDesc': pureldap.LDAPAttributeDescription('cn'), 'assertionValue': pureldap.LDAPAssertionValue('foo'), }, pureldap.LDAPBERDecoderContext_Filter( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext())), [0xa3, 9] + ([0x04, 2] + l(b'cn') + [0x04, 3] + l(b'foo')) ), (pureldap.LDAPFilter_or, [[pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('cn'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('uid'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('mail'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_substrings(type='mail', substrings=[pureldap.LDAPFilter_substrings_initial('foo@')]), ]], {}, pureldap.LDAPBERDecoderContext_Filter( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext())), [0xA1, 52] + ([0xa3, 9] + ([0x04, 2] + l(b'cn') + [0x04, 3] + l(b'foo')) + [0xa3, 10] + ([0x04, 3] + l(b'uid') + [0x04, 3] + l(b'foo')) + [0xa3, 11] + ([0x04, 4] + l(b'mail') + [0x04, 3] + l(b'foo')) + [0xa4, 14] + ([0x04, 4] + l(b'mail') + [0x30, 6] + ([0x80, 4] + l(b'foo@')))) ), (pureldap.LDAPSearchRequest, [], {'baseObject': 'dc=example,dc=com', 'scope': pureldap.LDAP_SCOPE_wholeSubtree, 'derefAliases': pureldap.LDAP_DEREF_neverDerefAliases, 'sizeLimit': 1, 'timeLimit': 0, 'typesOnly': False, 'filter': pureldap.LDAPFilter_or([ pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('cn'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('uid'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('mail'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_substrings(type='mail', substrings=[pureldap.LDAPFilter_substrings_initial('foo@')]), ]), 'attributes': [''], }, pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext())), [0x63, 92] + ([0x04, 17] + l(b'dc=example,dc=com') + [0x0a, 1, 0x02] + [0x0a, 1, 0x00] + [0x02, 1, 0x01] + [0x02, 1, 0x00] + [0x01, 1, 0x00] + [0xA1, 52] + ([0xa3, 9] + ([0x04, 2] + l(b'cn') + [0x04, 3] + l(b'foo')) + [0xa3, 10] + ([0x04, 3] + l(b'uid') + [0x04, 3] + l(b'foo')) + [0xa3, 11] + ([0x04, 4] + l(b'mail') + [0x04, 3] + l(b'foo')) + [0xa4, 14] + ([0x04, 4] + l(b'mail') + [0x30, 6] + ([0x80, 4] + l(b'foo@')))) + [0x30, 2] + ([0x04, 0]) ) ), (pureldap.LDAPMessage, [], {'id': 1, 'value': pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', scope=pureldap.LDAP_SCOPE_wholeSubtree, derefAliases=pureldap.LDAP_DEREF_neverDerefAliases, sizeLimit=1, timeLimit=0, typesOnly=False, filter=pureldap.LDAPFilter_or([ pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('cn'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('uid'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_equalityMatch(attributeDesc=pureldap.LDAPAttributeDescription('mail'), assertionValue=pureldap.LDAPAssertionValue('foo')), pureldap.LDAPFilter_substrings(type='mail', substrings=[pureldap.LDAPFilter_substrings_initial('foo@')]), ]), attributes=[''], ), }, pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()))), [0x30, 97] # id + [0x02, 1, 1] # value + [0x63, 92] + ([0x04, 17] + l(b'dc=example,dc=com') + [0x0a, 1, 0x02] + [0x0a, 1, 0x00] + [0x02, 1, 0x01] + [0x02, 1, 0x00] + [0x01, 1, 0x00] + [0xA1, 52] + ([0xa3, 9] + ([0x04, 2] + l(b'cn') + [0x04, 3] + l(b'foo')) + [0xa3, 10] + ([0x04, 3] + l(b'uid') + [0x04, 3] + l(b'foo')) + [0xa3, 11] + ([0x04, 4] + l(b'mail') + [0x04, 3] + l(b'foo')) + [0xa4, 14] + ([0x04, 4] + l(b'mail') + [0x30, 6] + ([0x80, 4] + l(b'foo@')))) + [0x30, 2] + ([0x04, 0]) ) ), (pureldap.LDAPExtendedRequest, [], {'requestName': '42.42.42', 'requestValue': 'foo', }, None, [0x40|0x20|23, 1+1+8+1+1+3] + ([0x80|0] + [len(b'42.42.42')] + l(b'42.42.42')) + ([0x80|1] + [len(b'foo')] + l(b'foo')) ), (pureldap.LDAPExtendedRequest, [], {'requestName': '42.42.42', 'requestValue': None, }, None, [0x40|0x20|23, 1+1+8] + ([0x80|0] + [len(b'42.42.42')] + l(b'42.42.42')) ), (pureldap.LDAPExtendedResponse, [], {'resultCode': 49, 'matchedDN': 'foo', 'errorMessage': 'bar', 'responseName': None, 'response': None, }, None, [0x40|0x20|24, 3+2+3+2+3, 0x0a, 1, 49, 0x04, len(b'foo')] + l(b'foo') + [ 0x04, len(b'bar')] + l(b'bar'), ), (pureldap.LDAPExtendedResponse, [], {'resultCode': 49, 'matchedDN': 'foo', 'errorMessage': 'bar', 'responseName': '1.2.3.4.5.6.7.8.9', 'response': 'baz', }, None, [0x40|0x20|24, 3+2+3+2+3+2+len('1.2.3.4.5.6.7.8.9')+2+3, 0x0a, 1, 49, 0x04, len(b'foo')] + l(b'foo') + [ 0x04, len(b'bar')] + l(b'bar') + [ 0x8a, len(b'1.2.3.4.5.6.7.8.9')] + l(b'1.2.3.4.5.6.7.8.9') + [ 0x8b, len(b'baz')] + l(b'baz'), ), (pureldap.LDAPAbandonRequest, [], {'id': 3}, None, [0x40|0x10, 0x01, 3] ), (pureldap.LDAPBindRequest, [], {'auth': ('PLAIN', 'test'), 'sasl': True}, pureldap.LDAPBERDecoderContext( fallback=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext(fallback=pureber.BERDecoderContext())), l(pureldap.LDAPBindRequest(auth=('PLAIN', 'test'), sasl=True).toWire()) ) ) def testToLDAP(self): """LDAPClass(...).toWire() should give known result with known input""" for klass, args, kwargs, decoder, encoded in self.knownValues: result = klass(*args, **kwargs) result = result.toWire() result = l(result) message = ( "Class %s(*%r, **%r) doesn't encode properly: " "%r != %r" % ( klass.__name__, args, kwargs, result, encoded)) self.assertEqual(encoded, result, message) def testFromLDAP(self): """LDAPClass(encoded="...") should give known result with known input""" for klass, args, kwargs, decoder, encoded in self.knownValues: if decoder is None: decoder = pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()) m=s(*encoded) result, bytes = pureber.berDecodeObject(decoder, m) self.assertEqual(bytes, len(m)) shouldBe = klass(*args, **kwargs) assert result.toWire() == shouldBe.toWire(), \ "Class %s(*%s, **%s) doesn't decode properly: " \ "%s != %s" % (klass.__name__, repr(args), repr(kwargs), repr(result), repr(shouldBe)) def testPartial(self): """LDAPClass(encoded="...") with too short input should throw BERExceptionInsufficientData""" for klass, args, kwargs, decoder, encoded in self.knownValues: if decoder is None: decoder = pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()) for i in six.moves.range(1, len(encoded)): m=s(*encoded)[:i] self.assertRaises(pureber.BERExceptionInsufficientData, pureber.berDecodeObject, decoder, m) self.assertEqual((None, 0), pureber.berDecodeObject(decoder, ''))
class LDAPServer(BaseLDAPServer): """An LDAP server""" boundUser = None fail_LDAPBindRequest = pureldap.LDAPBindResponse def handle_LDAPBindRequest(self, request, controls, reply): if request.version != 3: raise ldaperrors.LDAPProtocolError('Version %u not supported' % request.version) self.checkControls(controls) if request.dn == '': # anonymous bind self.boundUser = None return pureldap.LDAPBindResponse(resultCode=0) else: dn = distinguishedname.DistinguishedName(request.dn) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _noEntry(fail): fail.trap(ldaperrors.LDAPNoSuchObject) return None d.addErrback(_noEntry) def _gotEntry(entry, auth): if entry is None: raise ldaperrors.LDAPInvalidCredentials d = entry.bind(auth) def _cb(entry): self.boundUser = entry msg = pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode, matchedDN=str(entry.dn)) return msg d.addCallback(_cb) return d d.addCallback(_gotEntry, request.auth) return d def handle_LDAPUnbindRequest(self, request, controls, reply): # explicitly do not check unsupported critical controls -- we # have no way to return an error, anyway. self.transport.loseConnection() def getRootDSE(self, request, reply): root = interfaces.IConnectedLDAPEntry(self.factory) reply( pureldap.LDAPSearchResultEntry( objectName='', attributes=[ ('supportedLDAPVersion', ['3']), ('namingContexts', [str(root.dn)]), ('supportedExtension', [ pureldap.LDAPPasswordModifyRequest.oid, ]), ], )) return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.Success.resultCode) def _cbSearchGotBase(self, base, dn, request, reply): def _sendEntryToClient(entry): reply( pureldap.LDAPSearchResultEntry( objectName=str(entry.dn), attributes=entry.items(), )) d = base.search(filterObject=request.filter, attributes=request.attributes, scope=request.scope, derefAliases=request.derefAliases, sizeLimit=request.sizeLimit, timeLimit=request.timeLimit, typesOnly=request.typesOnly, callback=_sendEntryToClient) def _done(_): return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.Success.resultCode) d.addCallback(_done) return d def _cbSearchLDAPError(self, reason): reason.trap(ldaperrors.LDAPException) return pureldap.LDAPSearchResultDone( resultCode=reason.value.resultCode) def _cbSearchOtherError(self, reason): return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.other, errorMessage=reason.getErrorMessage()) fail_LDAPSearchRequest = pureldap.LDAPSearchResultDone def handle_LDAPSearchRequest(self, request, controls, reply): self.checkControls(controls) if (request.baseObject == '' and request.scope == pureldap.LDAP_SCOPE_baseObject and request.filter == pureldap.LDAPFilter_present('objectClass')): return self.getRootDSE(request, reply) dn = distinguishedname.DistinguishedName(request.baseObject) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) d.addCallback(self._cbSearchGotBase, dn, request, reply) d.addErrback(self._cbSearchLDAPError) d.addErrback(defer.logError) d.addErrback(self._cbSearchOtherError) return d fail_LDAPDelRequest = pureldap.LDAPDelResponse def handle_LDAPDelRequest(self, request, controls, reply): self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.value) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _gotEntry(entry): d = entry.delete() return d def _report(entry): return pureldap.LDAPDelResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPAddRequest = pureldap.LDAPAddResponse def handle_LDAPAddRequest(self, request, controls, reply): self.checkControls(controls) attributes = {} for name, vals in request.attributes: attributes.setdefault(name.value, set()) attributes[name.value].update([x.value for x in vals]) dn = distinguishedname.DistinguishedName(request.entry) rdn = str(dn.split()[0]) parent = dn.up() root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(parent) def _gotEntry(parent): d = parent.addChild(rdn, attributes) return d def _report(entry): return pureldap.LDAPAddResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPModifyDNRequest = pureldap.LDAPModifyDNResponse def handle_LDAPModifyDNRequest(self, request, controls, reply): self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.entry) newrdn = distinguishedname.RelativeDistinguishedName(request.newrdn) deleteoldrdn = bool(request.deleteoldrdn) if not deleteoldrdn: raise ldaperrors.LDAPUnwillingToPerform( "Cannot handle preserving old RDN yet.") newSuperior = request.newSuperior if newSuperior is None: newSuperior = dn.up() else: newSuperior = distinguishedname.DistinguishedName(newSuperior) newdn = distinguishedname.DistinguishedName(listOfRDNs=(newrdn, ) + newSuperior.split()) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _gotEntry(entry): d = entry.move(newdn) return d def _report(entry): return pureldap.LDAPModifyDNResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPModifyRequest = pureldap.LDAPModifyResponse def handle_LDAPModifyRequest(self, request, controls, reply): self.checkControls(controls) root = interfaces.IConnectedLDAPEntry(self.factory) mod = delta.ModifyOp.fromLDAP(request) d = mod.patch(root) def _patched(entry): return entry.commit() def _report(entry): return pureldap.LDAPModifyResponse(resultCode=0) d.addCallback(_patched) d.addCallback(_report) return d fail_LDAPExtendedRequest = pureldap.LDAPExtendedResponse def handle_LDAPExtendedRequest(self, request, controls, reply): self.checkControls(controls) for handler in [ getattr(self, attr) for attr in dir(self) if attr.startswith('extendedRequest_') ]: if getattr(handler, 'oid', None) == request.requestName: berdecoder = getattr(handler, 'berdecoder', None) if berdecoder is None: values = [request.requestValue] else: values = pureber.berDecodeMultiple(request.requestValue, berdecoder) d = defer.maybeDeferred(handler, *values, **{'reply': reply}) def eb(fail, oid): fail.trap(ldaperrors.LDAPException) return pureldap.LDAPExtendedResponse( resultCode=fail.value.resultCode, errorMessage=fail.value.message, responseName=oid, ) d.addErrback(eb, request.requestName) return d raise ldaperrors.LDAPProtocolError('Unknown extended request: %s' % request.requestName) def extendedRequest_LDAPPasswordModifyRequest(self, data, reply): if not isinstance(data, pureber.BERSequence): raise ldaperrors.LDAPProtocolError( 'Extended request PasswordModify expected a BERSequence.') userIdentity = None oldPasswd = None newPasswd = None for value in data: if isinstance(value, pureldap.LDAPPasswordModifyRequest_userIdentity): if userIdentity is not None: raise ldaperrors.LDAPProtocolError( 'Extended request ' 'PasswordModify received userIdentity twice.') userIdentity = value.value elif isinstance(value, pureldap.LDAPPasswordModifyRequest_oldPasswd): if oldPasswd is not None: raise ldaperrors.LDAPProtocolError( 'Extended request PasswordModify ' 'received oldPasswd twice.') oldPasswd = value.value elif isinstance(value, pureldap.LDAPPasswordModifyRequest_newPasswd): if newPasswd is not None: raise ldaperrors.LDAPProtocolError( 'Extended request PasswordModify ' 'received newPasswd twice.') newPasswd = value.value else: raise ldaperrors.LDAPProtocolError( 'Extended request PasswordModify ' 'received unexpected item.') if self.boundUser is None: raise ldaperrors.LDAPStrongAuthRequired() if (userIdentity is not None and userIdentity != self.boundUser.dn): log.msg('User %(actor)s tried to change password of %(target)s' % { 'actor': str(self.boundUser.dn), 'target': str(userIdentity), }) raise ldaperrors.LDAPInsufficientAccessRights() if (oldPasswd is not None or newPasswd is None): raise ldaperrors.LDAPOperationsError( 'Password does not support this case.') self.boundUser.setPassword(newPasswd) d = self.boundUser.commit() def cb_(result): if result: return pureldap.LDAPExtendedResponse( resultCode=ldaperrors.Success.resultCode, responseName=self. extendedRequest_LDAPPasswordModifyRequest.oid) else: raise ldaperrors.LDAPOperationsError('Internal error.') d.addCallback(cb_) return d extendedRequest_LDAPPasswordModifyRequest.oid = pureldap.LDAPPasswordModifyRequest.oid extendedRequest_LDAPPasswordModifyRequest.berdecoder = ( pureber.BERDecoderContext( inherit=pureldap.LDAPBERDecoderContext_LDAPPasswordModifyRequest( inherit=pureber.BERDecoderContext())))
class LDAPClient(protocol.Protocol): """An LDAP client""" debug = False def __init__(self): self.onwire = {} self.buffer = b'' self.connected = None berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()))) def dataReceived(self, recd): self.buffer += recd while 1: try: o, bytes = pureber.berDecodeObject(self.berdecoder, self.buffer) except pureber.BERExceptionInsufficientData: o, bytes = None, 0 self.buffer = self.buffer[bytes:] if not o: break self.handle(o) def connectionMade(self): """TCP connection has opened""" self.connected = 1 def connectionLost(self, reason=protocol.connectionDone): """Called when TCP connection has been lost""" self.connected = 0 # notify handlers of operations in flight while self.onwire: k, v = self.onwire.popitem() d, _, _, _, _ = v d.errback(reason) def _send(self, op, controls=None): if not self.connected: raise LDAPClientConnectionLostException() msg = pureldap.LDAPMessage(op, controls=controls) if self.debug: log.msg('C->S %s' % repr(msg)) assert msg.id not in self.onwire return msg def send(self, op, controls=None): """ Send an LDAP operation to the server. @param op: the operation to send @type op: LDAPProtocolRequest @param controls: Any controls to be included in the request. @type controls: LDAPControls @return: the response from server @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op, controls=controls) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, False, None, None, None) self.transport.write(msg.toWire()) return d def send_multiResponse(self, op, handler, *args, **kwargs): """ Send an LDAP operation to the server, expecting one or more responses. If `handler` is provided, it will receive a LDAP response as its first argument. The Deferred returned by this function will never fire. If `handler` is not provided, the Deferred returned by this function will fire with the final LDAP response. @param op: the operation to send @type op: LDAPProtocolRequest @param handler: a callable that will be called for each response. It should return a boolean, whether this was the final response. @param args: positional arguments to pass to handler @param kwargs: keyword arguments to pass to handler @return: the result from the first handler as a deferred that completes when the first response has been received @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, False, handler, args, kwargs) self.transport.write(msg.toWire()) return d def send_multiResponse_ex(self, op, controls=None, handler=None, *args, **kwargs): """ Send an LDAP operation to the server, expecting one or more responses. If `handler` is provided, it will receive a LDAP response *and* response controls as its first 2 arguments. The Deferred returned by this function will never fire. If `handler` is not provided, the Deferred returned by this function will fire with a tuple of the first LDAP response and any associated response controls. @param op: the operation to send @type op: LDAPProtocolRequest @param controls: LDAP controls to send with the message. @type controls: LDAPControls @param handler: a callable that will be called for each response. It should return a boolean, whether this was the final response. @param args: positional arguments to pass to handler @param kwargs: keyword arguments to pass to handler @return: the result from the last handler as a deferred that completes when the last response has been received @rtype: Deferred LDAPProtocolResponse """ msg = self._send(op, controls=controls) assert op.needs_answer d = defer.Deferred() self.onwire[msg.id] = (d, True, handler, args, kwargs) self.transport.write(msg.toWire()) return d def send_noResponse(self, op, controls=None): """ Send an LDAP operation to the server, with no response expected. @param op: the operation to send @type op: LDAPProtocolRequest """ msg = self._send(op, controls=controls) assert not op.needs_answer self.transport.write(msg.toWire()) def unsolicitedNotification(self, msg): log.msg("Got unsolicited notification: %s" % repr(msg)) def handle(self, msg): assert isinstance(msg.value, pureldap.LDAPProtocolResponse) if self.debug: log.msg('C<-S %s' % repr(msg)) if msg.id == 0: self.unsolicitedNotification(msg.value) else: d, return_controls, handler, args, kwargs = self.onwire[msg.id] if handler is None: assert (args is None) or (args == ()) assert (kwargs is None) or (kwargs == {}) if return_controls: d.callback((msg.value, msg.controls)) else: d.callback(msg.value) del self.onwire[msg.id] else: assert args is not None assert kwargs is not None # Return true to mark request as fully handled if return_controls: if handler(msg.value, msg.controls, *args, **kwargs): del self.onwire[msg.id] else: if handler(msg.value, *args, **kwargs): del self.onwire[msg.id] def bind(self, dn='', auth=''): """ @depreciated: Use e.bind(auth). @todo: Remove this method when there are no callers. """ if not self.connected: raise LDAPClientConnectionLostException() else: r = pureldap.LDAPBindRequest(dn=dn, auth=auth) d = self.send(r) d.addCallback(self._handle_bind_msg) return d def _handle_bind_msg(self, msg): assert isinstance(msg, pureldap.LDAPBindResponse) assert msg.referral is None # TODO if msg.resultCode != ldaperrors.Success.resultCode: raise ldaperrors.get(msg.resultCode, msg.errorMessage) return (msg.matchedDN, msg.serverSaslCreds) def unbind(self): if not self.connected: raise Exception( "Not connected (TODO)") # TODO make this a real object r = pureldap.LDAPUnbindRequest() self.send_noResponse(r) self.transport.loseConnection() def _cbStartTLS(self, msg, ctx): assert isinstance(msg, pureldap.LDAPExtendedResponse) assert msg.referral is None # TODO if msg.resultCode != ldaperrors.Success.resultCode: raise ldaperrors.get(msg.resultCode, msg.errorMessage) if (msg.responseName is not None) and \ (msg.responseName != pureldap.LDAPStartTLSResponse.oid): raise LDAPStartTLSInvalidResponseName(msg.responseName) self.transport.startTLS(ctx) return self def startTLS(self, ctx=None): """ Start Transport Layer Security. It is the callers responsibility to make sure other things are not happening at the same time. @todo: server hostname check, see rfc2830 section 3.6. @return: a deferred that will complete when the TLS handshake is complete. """ if ctx is None: ctx = ssl.ClientContextFactory() # we always delay by one event loop iteration to make # sure the previous handler has exited and self.onwire # has been cleaned up d = defer.Deferred() d.addCallback(self._startTLS) reactor.callLater(0, d.callback, ctx) return d def _startTLS(self, ctx): if not self.connected: raise LDAPClientConnectionLostException() elif self.onwire: raise LDAPStartTLSBusyError(self.onwire) else: op = pureldap.LDAPStartTLSRequest() d = self.send(op) d.addCallback(self._cbStartTLS, ctx) return d
class ServiceBindingProxy(unittest.TestCase): berdecoder = pureldap.LDAPBERDecoderContext_TopLevel( inherit=pureldap.LDAPBERDecoderContext_LDAPMessage( fallback=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()), inherit=pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()))) def createServer(self, services, fallback=None, responses=[]): server = testutil.createServer( lambda config: svcbindproxy.ServiceBindingProxy( config=config, services=services, fallback=fallback, ), baseDN='dc=example,dc=com', *responses) server.now = '20050213140302Z' server.timestamp = lambda: server.now return server def test_bind_noMatchingServicesFound_noFallback(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=False, responses=[ [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='s3krit'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc2)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc3)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), ) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode), id=4))) def test_bind_noMatchingServicesFound_fallback_success(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=True, responses=[ [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode) ], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='s3krit'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc2)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc3)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest(dn='cn=jack,dc=example,dc=com', auth='s3krit')) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode), id=4))) def test_bind_noMatchingServicesFound_fallback_badAuth(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=True, responses=[ [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode ), ], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='wrong-s3krit'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc2)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc3)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest(dn='cn=jack,dc=example,dc=com', auth='wrong-s3krit')) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode), id=4))) def test_bind_match_success(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=True, responses=[ # svc1 [ pureldap.LDAPSearchResultEntry( r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', attributes=[]), pureldap.LDAPSearchResultDone( ldaperrors.Success.resultCode) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode) ], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='secret'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest( dn= r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', auth='secret'), ) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode, matchedDN='cn=jack,dc=example,dc=com'), id=4))) def test_bind_match_success_later(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=True, responses=[ # svc1 [ pureldap.LDAPSearchResultEntry( r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', attributes=[]), pureldap.LDAPSearchResultDone( ldaperrors.Success.resultCode) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode ) ], # svc2 [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], # svc3 [ pureldap.LDAPSearchResultEntry( r'cn=svc3+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', attributes=[]), pureldap.LDAPSearchResultDone( ldaperrors.Success.resultCode) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode) ], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='secret'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest( dn= r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', auth='secret'), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc2)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc3)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest( dn= 'cn=svc3+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', auth='secret'), ) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode, matchedDN='cn=jack,dc=example,dc=com'), id=4))) def test_bind_match_badAuth(self): server = self.createServer( services=[ 'svc1', 'svc2', 'svc3', ], fallback=True, responses=[ # svc1 [ pureldap.LDAPSearchResultEntry( r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', attributes=[]), pureldap.LDAPSearchResultDone( ldaperrors.Success.resultCode) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode ) ], # svc2 [pureldap.LDAPSearchResultDone(ldaperrors.Success.resultCode)], # svc3 [ pureldap.LDAPSearchResultEntry( r'cn=svc3+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', attributes=[]), pureldap.LDAPSearchResultDone( ldaperrors.Success.resultCode) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode ) ], [ pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode ) ], ]) server.dataReceived( str( pureldap.LDAPMessage(pureldap.LDAPBindRequest( dn='cn=jack,dc=example,dc=com', auth='wrong-s3krit'), id=4))) reactor.iterate() #TODO client = server.client client.assertSent( pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc1)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest( dn= r'cn=svc1+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', auth='wrong-s3krit'), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc2)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPSearchRequest( baseObject='dc=example,dc=com', derefAliases=0, sizeLimit=0, timeLimit=0, typesOnly=0, filter=ldapfilter.parseFilter( '(&' + '(objectClass=serviceSecurityObject)' + '(owner=cn=jack,dc=example,dc=com)' + '(cn=svc3)' + ('(|(!(validFrom=*))(validFrom<=%s))' % server.now) + ('(|(!(validUntil=*))(validUntil>=%s))' % server.now) + ')'), attributes=('1.1', )), pureldap.LDAPBindRequest( dn= 'cn=svc3+owner=cn\=jack\,dc\=example\,dc\=com,dc=example,dc=com', auth='wrong-s3krit'), pureldap.LDAPBindRequest(version=3, dn='cn=jack,dc=example,dc=com', auth='wrong-s3krit'), ) self.assertEqual( server.transport.value(), str( pureldap.LDAPMessage(pureldap.LDAPBindResponse( resultCode=ldaperrors.LDAPInvalidCredentials.resultCode), id=4)))
def testPartialBERNullEncodings(self): """BERNull(encoded="...") with too short input should throw BERExceptionInsufficientData""" m=str(pureber.BERNull()) assert len(m)==2 self.assertRaises(pureber.BERExceptionInsufficientData, pureber.berDecodeObject, pureber.BERDecoderContext(), m[:1]) self.assertEquals((None, 0), pureber.berDecodeObject(pureber.BERDecoderContext(), ''))
class LDAPServer(BaseLDAPServer): """An LDAP server""" boundUser = None fail_LDAPBindRequest = pureldap.LDAPBindResponse def handle_LDAPBindRequest(self, request, controls, reply): if request.version != 3: raise ldaperrors.LDAPProtocolError("Version %u not supported" % request.version) self.checkControls(controls) if request.dn == b"": # anonymous bind self.boundUser = None return pureldap.LDAPBindResponse(resultCode=0) else: dn = distinguishedname.DistinguishedName(request.dn) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _noEntry(fail): fail.trap(ldaperrors.LDAPNoSuchObject) return None d.addErrback(_noEntry) def _gotEntry(entry, auth): if entry is None: raise ldaperrors.LDAPInvalidCredentials() d = entry.bind(auth) def _cb(entry): self.boundUser = entry msg = pureldap.LDAPBindResponse( resultCode=ldaperrors.Success.resultCode, matchedDN=entry.dn.getText(), ) return msg d.addCallback(_cb) return d d.addCallback(_gotEntry, request.auth) return d def handle_LDAPUnbindRequest(self, request, controls, reply): # explicitly do not check unsupported critical controls -- we # have no way to return an error, anyway. self.transport.loseConnection() def getRootDSE(self, request, reply): root = interfaces.IConnectedLDAPEntry(self.factory) reply( pureldap.LDAPSearchResultEntry( objectName="", attributes=[ ("supportedLDAPVersion", ["3"]), ("namingContexts", [root.dn.getText()]), ( "supportedExtension", [ pureldap.LDAPPasswordModifyRequest.oid, ], ), ], )) return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.Success.resultCode) fail_LDAPCompareRequest = pureldap.LDAPCompareResponse def handle_LDAPCompareRequest(self, request, controls, reply): def _cbCompareGotBase(base, ava, reply): def _done(result_list): if result_list: resultCode = ldaperrors.LDAPCompareTrue.resultCode else: resultCode = ldaperrors.LDAPCompareFalse.resultCode return pureldap.LDAPCompareResponse(resultCode) # base.search only works with Filter Objects, and not with # AttributeValueAssertion objects. Here we convert the AVA to an # equivalent Filter so we can re-use the existing search # functionality we require. search_filter = pureldap.LDAPFilter_equalityMatch( attributeDesc=ava.attributeDesc, assertionValue=ava.assertionValue) d = base.search( filterObject=search_filter, scope=pureldap.LDAP_SCOPE_baseObject, derefAliases=pureldap.LDAP_DEREF_neverDerefAliases, ) d.addCallback(_done) return d def _cbCompareLDAPError(reason): reason.trap(ldaperrors.LDAPException) return pureldap.LDAPCompareResponse( resultCode=reason.value.resultCode) def _cbCompareOtherError(reason): return pureldap.LDAPCompareResponse( resultCode=ldaperrors.other, errorMessage=reason.getErrorMessage()) self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.entry) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) d.addCallback(_cbCompareGotBase, request.ava, reply) d.addErrback(_cbCompareLDAPError) d.addErrback(defer.logError) d.addErrback(_cbCompareOtherError) return d def _cbSearchGotBase(self, base, dn, request, reply): def _sendEntryToClient(entry): requested_attribs = request.attributes if len(requested_attribs) > 0 and b"*" not in requested_attribs: filtered_attribs = [(k, entry.get(k)) for k in requested_attribs if k in entry] else: filtered_attribs = entry.items() reply( pureldap.LDAPSearchResultEntry( objectName=entry.dn.getText(), attributes=filtered_attribs, )) d = base.search( filterObject=request.filter, attributes=request.attributes, scope=request.scope, derefAliases=request.derefAliases, sizeLimit=request.sizeLimit, timeLimit=request.timeLimit, typesOnly=request.typesOnly, callback=_sendEntryToClient, ) def _done(_): return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.Success.resultCode) d.addCallback(_done) return d def _cbSearchLDAPError(self, reason): reason.trap(ldaperrors.LDAPException) return pureldap.LDAPSearchResultDone( resultCode=reason.value.resultCode) def _cbSearchOtherError(self, reason): return pureldap.LDAPSearchResultDone( resultCode=ldaperrors.other, errorMessage=reason.getErrorMessage()) fail_LDAPSearchRequest = pureldap.LDAPSearchResultDone def handle_LDAPSearchRequest(self, request, controls, reply): self.checkControls(controls) if (request.baseObject == b"" and request.scope == pureldap.LDAP_SCOPE_baseObject and request.filter == pureldap.LDAPFilter_present("objectClass")): return self.getRootDSE(request, reply) dn = distinguishedname.DistinguishedName(request.baseObject) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) d.addCallback(self._cbSearchGotBase, dn, request, reply) d.addErrback(self._cbSearchLDAPError) d.addErrback(defer.logError) d.addErrback(self._cbSearchOtherError) return d fail_LDAPDelRequest = pureldap.LDAPDelResponse def handle_LDAPDelRequest(self, request, controls, reply): self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.value) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _gotEntry(entry): d = entry.delete() return d def _report(entry): return pureldap.LDAPDelResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPAddRequest = pureldap.LDAPAddResponse def handle_LDAPAddRequest(self, request, controls, reply): self.checkControls(controls) attributes = {} for name, vals in request.attributes: attributes.setdefault(name.value, set()) attributes[name.value].update([x.value for x in vals]) dn = distinguishedname.DistinguishedName(request.entry) rdn = dn.split()[0].getText() parent = dn.up() root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(parent) def _gotEntry(parent): d = parent.addChild(rdn, attributes) return d def _report(entry): return pureldap.LDAPAddResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPModifyDNRequest = pureldap.LDAPModifyDNResponse def handle_LDAPModifyDNRequest(self, request, controls, reply): self.checkControls(controls) dn = distinguishedname.DistinguishedName(request.entry) newrdn = distinguishedname.RelativeDistinguishedName(request.newrdn) deleteoldrdn = bool(request.deleteoldrdn) if not deleteoldrdn: raise ldaperrors.LDAPUnwillingToPerform( "Cannot handle preserving old RDN yet.") newSuperior = request.newSuperior if newSuperior is None: newSuperior = dn.up() else: newSuperior = distinguishedname.DistinguishedName(newSuperior) newdn = distinguishedname.DistinguishedName(listOfRDNs=(newrdn, ) + newSuperior.split()) root = interfaces.IConnectedLDAPEntry(self.factory) d = root.lookup(dn) def _gotEntry(entry): d = entry.move(newdn) return d def _report(entry): return pureldap.LDAPModifyDNResponse(resultCode=0) d.addCallback(_gotEntry) d.addCallback(_report) return d fail_LDAPModifyRequest = pureldap.LDAPModifyResponse def handle_LDAPModifyRequest(self, request, controls, reply): self.checkControls(controls) root = interfaces.IConnectedLDAPEntry(self.factory) mod = delta.ModifyOp.fromLDAP(request) d = mod.patch(root) def _patched(entry): return entry.commit() def _report(entry): return pureldap.LDAPModifyResponse(resultCode=0) d.addCallback(_patched) d.addCallback(_report) return d fail_LDAPExtendedRequest = pureldap.LDAPExtendedResponse def handle_LDAPExtendedRequest(self, request, controls, reply): self.checkControls(controls) for handler in [ getattr(self, attr) for attr in dir(self) if attr.startswith("extendedRequest_") ]: if getattr(handler, "oid", None) == request.requestName: berdecoder = getattr(handler, "berdecoder", None) if berdecoder is None: values = [request.requestValue] else: values = pureber.berDecodeMultiple(request.requestValue, berdecoder) d = defer.maybeDeferred(handler, *values, **{"reply": reply}) def eb(fail, oid): fail.trap(ldaperrors.LDAPException) return pureldap.LDAPExtendedResponse( resultCode=fail.value.resultCode, errorMessage=fail.value.message, responseName=oid, ) d.addErrback(eb, request.requestName) return d raise ldaperrors.LDAPProtocolError(b"Unknown extended request: %s" % request.requestName) def extendedRequest_LDAPPasswordModifyRequest(self, data, reply): if not isinstance(data, pureber.BERSequence): raise ldaperrors.LDAPProtocolError( "Extended request PasswordModify expected a BERSequence.") userIdentity = None oldPasswd = None newPasswd = None for value in data: if isinstance(value, pureldap.LDAPPasswordModifyRequest_userIdentity): if userIdentity is not None: raise ldaperrors.LDAPProtocolError( "Extended request " "PasswordModify received userIdentity twice.") userIdentity = value.value elif isinstance(value, pureldap.LDAPPasswordModifyRequest_oldPasswd): if oldPasswd is not None: raise ldaperrors.LDAPProtocolError( "Extended request PasswordModify " "received oldPasswd twice.") oldPasswd = value.value elif isinstance(value, pureldap.LDAPPasswordModifyRequest_newPasswd): if newPasswd is not None: raise ldaperrors.LDAPProtocolError( "Extended request PasswordModify " "received newPasswd twice.") newPasswd = value.value else: raise ldaperrors.LDAPProtocolError( "Extended request PasswordModify " "received unexpected item.") if self.boundUser is None: raise ldaperrors.LDAPStrongAuthRequired() if userIdentity is not None and userIdentity != self.boundUser.dn: log.msg("User {actor} tried to change password of {target}".format( actor=self.boundUser.dn.getText(), target=userIdentity, )) raise ldaperrors.LDAPInsufficientAccessRights() if oldPasswd is not None or newPasswd is None: raise ldaperrors.LDAPOperationsError( "Password does not support this case.") self.boundUser.setPassword(newPasswd) d = self.boundUser.commit() def cb_(result): if result: return pureldap.LDAPExtendedResponse( resultCode=ldaperrors.Success.resultCode, responseName=self. extendedRequest_LDAPPasswordModifyRequest.oid, ) else: raise ldaperrors.LDAPOperationsError("Internal error.") d.addCallback(cb_) return d extendedRequest_LDAPPasswordModifyRequest.oid = ( pureldap.LDAPPasswordModifyRequest.oid) extendedRequest_LDAPPasswordModifyRequest.berdecoder = pureber.BERDecoderContext( inherit=pureldap.LDAPBERDecoderContext_LDAPPasswordModifyRequest( inherit=pureber.BERDecoderContext()))