teleport/server/www/packages/packages-windows/x86/ldap3/strategy/mockBase.py

902 lines
44 KiB
Python

"""
"""
# Created on 2016.04.30
#
# Author: Giovanni Cannata
#
# Copyright 2016 - 2018 Giovanni Cannata
#
# This file is part of ldap3.
#
# ldap3 is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ldap3 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ldap3 in the COPYING and COPYING.LESSER files.
# If not, see <http://www.gnu.org/licenses/>.
import json
import re
from threading import Lock
from random import SystemRandom
from pyasn1.type.univ import OctetString
from .. import SEQUENCE_TYPES, ALL_ATTRIBUTES
from ..operation.bind import bind_request_to_dict
from ..operation.delete import delete_request_to_dict
from ..operation.add import add_request_to_dict
from ..operation.compare import compare_request_to_dict
from ..operation.modifyDn import modify_dn_request_to_dict
from ..operation.modify import modify_request_to_dict
from ..operation.extended import extended_request_to_dict
from ..operation.search import search_request_to_dict, parse_filter, ROOT, AND, OR, NOT, MATCH_APPROX, \
MATCH_GREATER_OR_EQUAL, MATCH_LESS_OR_EQUAL, MATCH_EXTENSIBLE, MATCH_PRESENT,\
MATCH_SUBSTRING, MATCH_EQUAL
from ..utils.conv import json_hook, to_unicode, to_raw
from ..core.exceptions import LDAPDefinitionError, LDAPPasswordIsMandatoryError, LDAPInvalidValueError, LDAPSocketOpenError
from ..core.results import RESULT_SUCCESS, RESULT_OPERATIONS_ERROR, RESULT_UNAVAILABLE_CRITICAL_EXTENSION, \
RESULT_INVALID_CREDENTIALS, RESULT_NO_SUCH_OBJECT, RESULT_ENTRY_ALREADY_EXISTS, RESULT_COMPARE_TRUE, \
RESULT_COMPARE_FALSE, RESULT_NO_SUCH_ATTRIBUTE, RESULT_UNWILLING_TO_PERFORM
from ..utils.ciDict import CaseInsensitiveDict
from ..utils.dn import to_dn, safe_dn, safe_rdn
from ..protocol.sasl.sasl import validate_simple_password
from ..protocol.formatters.standard import find_attribute_validator, format_attribute_values
from ..protocol.rfc2696 import paged_search_control
from ..utils.log import log, log_enabled, ERROR, BASIC
from ..utils.asn1 import encode
from ..utils.conv import ldap_escape_to_bytes
from ..strategy.base import BaseStrategy # needed for decode_control() method
from ..protocol.rfc4511 import LDAPMessage, ProtocolOp, MessageID
from ..protocol.convert import build_controls_list
# LDAPResult ::= SEQUENCE {
# resultCode ENUMERATED {
# success (0),
# operationsError (1),
# protocolError (2),
# timeLimitExceeded (3),
# sizeLimitExceeded (4),
# compareFalse (5),
# compareTrue (6),
# authMethodNotSupported (7),
# strongerAuthRequired (8),
# -- 9 reserved --
# referral (10),
# adminLimitExceeded (11),
# unavailableCriticalExtension (12),
# confidentialityRequired (13),
# saslBindInProgress (14),
# noSuchAttribute (16),
# undefinedAttributeType (17),
# inappropriateMatching (18),
# constraintViolation (19),
# attributeOrValueExists (20),
# invalidAttributeSyntax (21),
# -- 22-31 unused --
# noSuchObject (32),
# aliasProblem (33),
# invalidDNSyntax (34),
# -- 35 reserved for undefined isLeaf --
# aliasDereferencingProblem (36),
# -- 37-47 unused --
# inappropriateAuthentication (48),
# invalidCredentials (49),
# insufficientAccessRights (50),
# busy (51),
# unavailable (52),
# unwillingToPerform (53),
# loopDetect (54),
# -- 55-63 unused --
# namingViolation (64),
# objectClassViolation (65),
# notAllowedOnNonLeaf (66),
# notAllowedOnRDN (67),
# entryAlreadyExists (68),
# objectClassModsProhibited (69),
# -- 70 reserved for CLDAP --
# affectsMultipleDSAs (71),
# -- 72-79 unused --
# other (80),
# ... },
# matchedDN LDAPDN,
# diagnosticMessage LDAPString,
# referral [3] Referral OPTIONAL }
# noinspection PyProtectedMember,PyUnresolvedReferences
SEARCH_CONTROLS = ['1.2.840.113556.1.4.319' # simple paged search [RFC 2696]
]
SERVER_ENCODING = 'utf-8'
def random_cookie():
return to_raw(SystemRandom().random())[-6:]
class PagedSearchSet(object):
def __init__(self, response, size, criticality):
self.size = size
self.response = response
self.cookie = None
self.sent = 0
self.done = False
def next(self, size=None):
if size:
self.size=size
message = ''
response = self.response[self.sent: self.sent + self.size]
self.sent += self.size
if self.sent > len(self.response):
self.done = True
self.cookie = ''
else:
self.cookie = random_cookie()
response_control = paged_search_control(False, len(self.response), self.cookie)
result = {'resultCode': RESULT_SUCCESS,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None,
'controls': [BaseStrategy.decode_control(response_control)]
}
return response, result
class MockBaseStrategy(object):
"""
Base class for connection strategy
"""
def __init__(self):
if not hasattr(self.connection.server, 'dit'): # create entries dict if not already present
self.connection.server.dit = CaseInsensitiveDict()
self.entries = self.connection.server.dit # for simpler reference
self.no_real_dsa = True
self.bound = None
self.custom_validators = None
self.operational_attributes = ['entryDN']
self.add_entry('cn=schema', [], validate=False) # add default entry for schema
self._paged_sets = [] # list of paged search in progress
if log_enabled(BASIC):
log(BASIC, 'instantiated <%s>: <%s>', self.__class__.__name__, self)
def _start_listen(self):
self.connection.listening = True
self.connection.closed = False
if self.connection.usage:
self.connection._usage.open_sockets += 1
def _stop_listen(self):
self.connection.listening = False
self.connection.closed = True
if self.connection.usage:
self.connection._usage.closed_sockets += 1
def _prepare_value(self, attribute_type, value, validate=True):
"""
Prepare a value for being stored in the mock DIT
:param value: object to store
:return: raw value to store in the DIT
"""
if validate: # if loading from json dump do not validate values:
validator = find_attribute_validator(self.connection.server.schema, attribute_type, self.custom_validators)
validated = validator(value)
if validated is False:
raise LDAPInvalidValueError('value non valid for attribute \'%s\'' % attribute_type)
elif validated is not True: # a valid LDAP value equivalent to the actual value
value = validated
raw_value = to_raw(value)
if not isinstance(raw_value, bytes):
raise LDAPInvalidValueError('The value "%s" of type %s for "%s" must be bytes or an offline schema needs to be provided when Mock strategy is used.' % (
value,
type(value),
attribute_type,
))
return raw_value
def _update_attribute(self, dn, attribute_type, value):
pass
def add_entry(self, dn, attributes, validate=True):
with self.connection.server.dit_lock:
escaped_dn = safe_dn(dn)
if escaped_dn not in self.connection.server.dit:
new_entry = CaseInsensitiveDict()
for attribute in attributes:
if attribute in self.operational_attributes: # no restore of operational attributes, should be computed at runtime
continue
if not isinstance(attributes[attribute], SEQUENCE_TYPES): # entry attributes are always lists of bytes values
attributes[attribute] = [attributes[attribute]]
if self.connection.server.schema and self.connection.server.schema.attribute_types[attribute].single_value and len(attributes[attribute]) > 1: # multiple values in single-valued attribute
return False
if attribute.lower() == 'objectclass' and self.connection.server.schema: # builds the objectClass hierarchy only if schema is present
class_set = set()
for object_class in attributes['objectClass']:
if self.connection.server.schema.object_classes and object_class not in self.connection.server.schema.object_classes:
return False
# walkups the class hierarchy and buils a set of all classes in it
class_set.add(object_class)
class_set_size = 0
while class_set_size != len(class_set):
new_classes = set()
class_set_size = len(class_set)
for class_name in class_set:
if self.connection.server.schema.object_classes[class_name].superior:
new_classes.update(self.connection.server.schema.object_classes[class_name].superior)
class_set.update(new_classes)
new_entry['objectClass'] = [to_raw(value) for value in class_set]
else:
new_entry[attribute] = [self._prepare_value(attribute, value, validate) for value in attributes[attribute]]
for rdn in safe_rdn(escaped_dn, decompose=True): # adds rdns to entry attributes
if rdn[0] not in new_entry: # if rdn attribute is missing adds attribute and its value
new_entry[rdn[0]] = [to_raw(rdn[1])]
else:
raw_rdn = to_raw(rdn[1])
if raw_rdn not in new_entry[rdn[0]]: # add rdn value if rdn attribute is present but value is missing
new_entry[rdn[0]].append(raw_rdn)
new_entry['entryDN'] = [to_raw(escaped_dn)]
self.connection.server.dit[escaped_dn] = new_entry
return True
return False
def remove_entry(self, dn):
with self.connection.server.dit_lock:
escaped_dn = safe_dn(dn)
if escaped_dn in self.connection.server.dit:
del self.connection.server.dit[escaped_dn]
return True
return False
def entries_from_json(self, json_entry_file):
target = open(json_entry_file, 'r')
definition = json.load(target, object_hook=json_hook)
if 'entries' not in definition:
self.connection.last_error = 'invalid JSON definition, missing "entries" section'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPDefinitionError(self.connection.last_error)
if not self.connection.server.dit:
self.connection.server.dit = CaseInsensitiveDict()
for entry in definition['entries']:
if 'raw' not in entry:
self.connection.last_error = 'invalid JSON definition, missing "raw" section'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPDefinitionError(self.connection.last_error)
if 'dn' not in entry:
self.connection.last_error = 'invalid JSON definition, missing "dn" section'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPDefinitionError(self.connection.last_error)
self.add_entry(entry['dn'], entry['raw'], validate=False)
target.close()
def mock_bind(self, request_message, controls):
# BindRequest ::= [APPLICATION 0] SEQUENCE {
# version INTEGER (1 .. 127),
# name LDAPDN,
# authentication AuthenticationChoice }
#
# BindResponse ::= [APPLICATION 1] SEQUENCE {
# COMPONENTS OF LDAPResult,
# serverSaslCreds [7] OCTET STRING OPTIONAL }
#
# request: version, name, authentication
# response: LDAPResult + serverSaslCreds
request = bind_request_to_dict(request_message)
identity = request['name']
if 'simple' in request['authentication']:
try:
password = validate_simple_password(request['authentication']['simple'])
except LDAPPasswordIsMandatoryError:
password = ''
identity = '<anonymous>'
else:
self.connection.last_error = 'only Simple Bind allowed in Mock strategy'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPDefinitionError(self.connection.last_error)
# checks userPassword for password. userPassword must be a text string or a list of text strings
if identity in self.connection.server.dit:
if 'userPassword' in self.connection.server.dit[identity]:
# if self.connection.server.dit[identity]['userPassword'] == password or password in self.connection.server.dit[identity]['userPassword']:
if self.equal(identity, 'userPassword', password):
result_code = RESULT_SUCCESS
message = ''
self.bound = identity
else:
result_code = RESULT_INVALID_CREDENTIALS
message = 'invalid credentials'
else: # no user found, returns invalidCredentials
result_code = RESULT_INVALID_CREDENTIALS
message = 'missing userPassword attribute'
elif identity == '<anonymous>':
result_code = RESULT_SUCCESS
message = ''
self.bound = identity
else:
result_code = RESULT_INVALID_CREDENTIALS
message = 'missing object'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None,
'serverSaslCreds': None
}
def mock_delete(self, request_message, controls):
# DelRequest ::= [APPLICATION 10] LDAPDN
#
# DelResponse ::= [APPLICATION 11] LDAPResult
#
# request: entry
# response: LDAPResult
request = delete_request_to_dict(request_message)
dn = safe_dn(request['entry'])
if dn in self.connection.server.dit:
del self.connection.server.dit[dn]
result_code = RESULT_SUCCESS
message = ''
else:
result_code = RESULT_NO_SUCH_OBJECT
message = 'object not found'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
def mock_add(self, request_message, controls):
# AddRequest ::= [APPLICATION 8] SEQUENCE {
# entry LDAPDN,
# attributes AttributeList }
#
# AddResponse ::= [APPLICATION 9] LDAPResult
#
# request: entry, attributes
# response: LDAPResult
request = add_request_to_dict(request_message)
dn = safe_dn(request['entry'])
attributes = request['attributes']
# converts attributes values to bytes
if dn not in self.connection.server.dit:
if self.add_entry(dn, attributes):
result_code = RESULT_SUCCESS
message = ''
else:
result_code = RESULT_OPERATIONS_ERROR
message = 'error adding entry'
else:
result_code = RESULT_ENTRY_ALREADY_EXISTS
message = 'entry already exist'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
def mock_compare(self, request_message, controls):
# CompareRequest ::= [APPLICATION 14] SEQUENCE {
# entry LDAPDN,
# ava AttributeValueAssertion }
#
# CompareResponse ::= [APPLICATION 15] LDAPResult
#
# request: entry, attribute, value
# response: LDAPResult
request = compare_request_to_dict(request_message)
dn = safe_dn(request['entry'])
attribute = request['attribute']
value = to_raw(request['value'])
if dn in self.connection.server.dit:
if attribute in self.connection.server.dit[dn]:
if self.equal(dn, attribute, value):
result_code = RESULT_COMPARE_TRUE
message = ''
else:
result_code = RESULT_COMPARE_FALSE
message = ''
else:
result_code = RESULT_NO_SUCH_ATTRIBUTE
message = 'attribute not found'
else:
result_code = RESULT_NO_SUCH_OBJECT
message = 'object not found'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
def mock_modify_dn(self, request_message, controls):
# ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
# entry LDAPDN,
# newrdn RelativeLDAPDN,
# deleteoldrdn BOOLEAN,
# newSuperior [0] LDAPDN OPTIONAL }
#
# ModifyDNResponse ::= [APPLICATION 13] LDAPResult
#
# request: entry, newRdn, deleteOldRdn, newSuperior
# response: LDAPResult
request = modify_dn_request_to_dict(request_message)
dn = safe_dn(request['entry'])
new_rdn = request['newRdn']
delete_old_rdn = request['deleteOldRdn']
new_superior = safe_dn(request['newSuperior']) if request['newSuperior'] else ''
dn_components = to_dn(dn)
if dn in self.connection.server.dit:
if new_superior and new_rdn: # performs move in the DIT
new_dn = safe_dn(dn_components[0] + ',' + new_superior)
self.connection.server.dit[new_dn] = self.connection.server.dit[dn].copy()
moved_entry = self.connection.server.dit[new_dn]
if delete_old_rdn:
del self.connection.server.dit[dn]
result_code = RESULT_SUCCESS
message = 'entry moved'
moved_entry['entryDN'] = [to_raw(new_dn)]
elif new_rdn and not new_superior: # performs rename
new_dn = safe_dn(new_rdn + ',' + safe_dn(dn_components[1:]))
self.connection.server.dit[new_dn] = self.connection.server.dit[dn].copy()
renamed_entry = self.connection.server.dit[new_dn]
del self.connection.server.dit[dn]
renamed_entry['entryDN'] = [to_raw(new_dn)]
for rdn in safe_rdn(new_dn, decompose=True): # adds rdns to entry attributes
renamed_entry[rdn[0]] = [to_raw(rdn[1])]
result_code = RESULT_SUCCESS
message = 'entry rdn renamed'
else:
result_code = RESULT_UNWILLING_TO_PERFORM
message = 'newRdn or newSuperior missing'
else:
result_code = RESULT_NO_SUCH_OBJECT
message = 'object not found'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
def mock_modify(self, request_message, controls):
# ModifyRequest ::= [APPLICATION 6] SEQUENCE {
# object LDAPDN,
# changes SEQUENCE OF change SEQUENCE {
# operation ENUMERATED {
# add (0),
# delete (1),
# replace (2),
# ... },
# modification PartialAttribute } }
#
# ModifyResponse ::= [APPLICATION 7] LDAPResult
#
# request: entry, changes
# response: LDAPResult
#
# changes is a dictionary in the form {'attribute': [(operation, [val1, ...]), ...], ...}
# operation is 0 (add), 1 (delete), 2 (replace), 3 (increment)
request = modify_request_to_dict(request_message)
dn = safe_dn(request['entry'])
changes = request['changes']
result_code = 0
message = ''
rdns = [rdn[0] for rdn in safe_rdn(dn, decompose=True)]
if dn in self.connection.server.dit:
entry = self.connection.server.dit[dn]
original_entry = entry.copy() # to preserve atomicity of operation
for modification in changes:
operation = modification['operation']
attribute = modification['attribute']['type']
elements = modification['attribute']['value']
if operation == 0: # add
if attribute not in entry and elements: # attribute not present, creates the new attribute and add elements
if self.connection.server.schema and self.connection.server.schema.attribute_types and self.connection.server.schema.attribute_types[attribute].single_value and len(elements) > 1: # multiple values in single-valued attribute
result_code = 19
message = 'attribute is single-valued'
else:
entry[attribute] = [to_raw(element) for element in elements]
else: # attribute present, adds elements to current values
if self.connection.server.schema and self.connection.server.schema.attribute_types and self.connection.server.schema.attribute_types[attribute].single_value: # multiple values in single-valued attribute
result_code = 19
message = 'attribute is single-valued'
else:
entry[attribute].extend([to_raw(element) for element in elements])
elif operation == 1: # delete
if attribute not in entry: # attribute must exist
result_code = RESULT_NO_SUCH_ATTRIBUTE
message = 'attribute must exists for deleting its values'
elif attribute in rdns: # attribute can't be used in dn
result_code = 67
message = 'cannot delete an rdn'
else:
if not elements: # deletes whole attribute if element list is empty
del entry[attribute]
else:
for element in elements:
raw_element = to_raw(element)
if self.equal(dn, attribute, raw_element): # removes single element
entry[attribute].remove(raw_element)
else:
result_code = 1
message = 'value to delete not found'
if not entry[attribute]: # removes the whole attribute if no elements remained
del entry[attribute]
elif operation == 2: # replace
if attribute not in entry and elements: # attribute not present, creates the new attribute and add elements
if self.connection.server.schema and self.connection.server.schema.attribute_types and self.connection.server.schema.attribute_types[attribute].single_value and len(elements) > 1: # multiple values in single-valued attribute
result_code = 19
message = 'attribute is single-valued'
else:
entry[attribute] = [to_raw(element) for element in elements]
elif not elements and attribute in rdns: # attribute can't be used in dn
result_code = 67
message = 'cannot replace an rdn'
elif not elements: # deletes whole attribute if element list is empty
if attribute in entry:
del entry[attribute]
else: # substitutes elements
entry[attribute] = [to_raw(element) for element in elements]
if result_code: # an error has happened, restores the original dn
self.connection.server.dit[dn] = original_entry
else:
result_code = RESULT_NO_SUCH_OBJECT
message = 'object not found'
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
def mock_search(self, request_message, controls):
# SearchRequest ::= [APPLICATION 3] SEQUENCE {
# baseObject LDAPDN,
# scope ENUMERATED {
# baseObject (0),
# singleLevel (1),
# wholeSubtree (2),
# ... },
# derefAliases ENUMERATED {
# neverDerefAliases (0),
# derefInSearching (1),
# derefFindingBaseObj (2),
# derefAlways (3) },
# sizeLimit INTEGER (0 .. maxInt),
# timeLimit INTEGER (0 .. maxInt),
# typesOnly BOOLEAN,
# filter Filter,
# attributes AttributeSelection }
#
# SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
# objectName LDAPDN,
# attributes PartialAttributeList }
#
#
# SearchResultReference ::= [APPLICATION 19] SEQUENCE
# SIZE (1..MAX) OF uri URI
#
# SearchResultDone ::= [APPLICATION 5] LDAPResult
#
# request: base, scope, dereferenceAlias, sizeLimit, timeLimit, typesOnly, filter, attributes
# response_entry: object, attributes
# response_done: LDAPResult
request = search_request_to_dict(request_message)
if controls:
decoded_controls = [self.decode_control(control) for control in controls if control]
for decoded_control in decoded_controls:
if decoded_control[1]['criticality'] and decoded_control[0] not in SEARCH_CONTROLS:
message = 'Critical requested control ' + str(decoded_control[0]) + ' not available'
result = {'resultCode': RESULT_UNAVAILABLE_CRITICAL_EXTENSION,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
return [], result
elif decoded_control[0] == '1.2.840.113556.1.4.319': # Simple paged search
if not decoded_control[1]['value']['cookie']: # new paged search
response, result = self._execute_search(request)
if result['resultCode'] == RESULT_SUCCESS: # success
paged_set = PagedSearchSet(response, int(decoded_control[1]['value']['size']), decoded_control[1]['criticality'])
response, result = paged_set.next()
if paged_set.done: # paged search already completed, no need to store the set
del paged_set
else:
self._paged_sets.append(paged_set)
return response, result
else:
return [], result
else:
for paged_set in self._paged_sets:
if paged_set.cookie == decoded_control[1]['value']['cookie']: # existing paged set
response, result = paged_set.next() # returns next bunch of entries as per paged set specifications
if paged_set.done:
self._paged_sets.remove(paged_set)
return response, result
# paged set not found
message = 'Invalid cookie in simple paged search'
result = {'resultCode': RESULT_OPERATIONS_ERROR,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
return [], result
else:
return self._execute_search(request)
def _execute_search(self, request):
responses = []
base = safe_dn(request['base'])
scope = request['scope']
attributes = request['attributes']
if '+' in attributes: # operational attributes requested
attributes.extend(self.operational_attributes)
attributes.remove('+')
attributes = [attr.lower() for attr in request['attributes']]
filter_root = parse_filter(request['filter'], self.connection.server.schema, auto_escape=True, auto_encode=False, validator=self.connection.server.custom_validator, check_names=self.connection.check_names)
candidates = []
if scope == 0: # base object
if base in self.connection.server.dit or base.lower() == 'cn=schema':
candidates.append(base)
elif scope == 1: # single level
for entry in self.connection.server.dit:
if entry.lower().endswith(base.lower()) and ',' not in entry[:-len(base) - 1]: # only leafs without commas in the remaining dn
candidates.append(entry)
elif scope == 2: # whole subtree
for entry in self.connection.server.dit:
if entry.lower().endswith(base.lower()):
candidates.append(entry)
if not candidates: # incorrect base
result_code = RESULT_NO_SUCH_OBJECT
message = 'incorrect base object'
else:
matched = self.evaluate_filter_node(filter_root, candidates)
if self.connection.raise_exceptions and 0 < request['sizeLimit'] < len(matched):
result_code = 4
message = 'size limit exceeded'
else:
for match in matched:
responses.append({
'object': match,
'attributes': [{'type': attribute,
'vals': [] if request['typesOnly'] else self.connection.server.dit[match][attribute]}
for attribute in self.connection.server.dit[match]
if attribute.lower() in attributes or ALL_ATTRIBUTES in attributes]
})
result_code = 0
message = ''
result = {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None
}
return responses[:request['sizeLimit']] if request['sizeLimit'] > 0 else responses, result
def mock_extended(self, request_message, controls):
# ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
# requestName [0] LDAPOID,
# requestValue [1] OCTET STRING OPTIONAL }
#
# ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
# COMPONENTS OF LDAPResult,
# responseName [10] LDAPOID OPTIONAL,
# responseValue [11] OCTET STRING OPTIONAL }
#
# IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
# responseName [0] LDAPOID OPTIONAL,
# responseValue [1] OCTET STRING OPTIONAL }
request = extended_request_to_dict(request_message)
result_code = RESULT_UNWILLING_TO_PERFORM
message = 'not implemented'
response_name = None
response_value = None
if self.connection.server.info:
for extension in self.connection.server.info.supported_extensions:
if request['name'] == extension[0]: # server can answer the extended request
if extension[0] == '2.16.840.1.113719.1.27.100.31': # getBindDNRequest [NOVELL]
result_code = 0
message = ''
response_name = '2.16.840.1.113719.1.27.100.32' # getBindDNResponse [NOVELL]
response_value = OctetString(self.bound)
elif extension[0] == '1.3.6.1.4.1.4203.1.11.3': # WhoAmI [RFC4532]
result_code = 0
message = ''
response_name = '1.3.6.1.4.1.4203.1.11.3' # WhoAmI [RFC4532]
response_value = OctetString(self.bound)
break
return {'resultCode': result_code,
'matchedDN': '',
'diagnosticMessage': to_unicode(message, SERVER_ENCODING),
'referral': None,
'responseName': response_name,
'responseValue': response_value
}
def evaluate_filter_node(self, node, candidates):
"""After evaluation each 2 sets are added to each MATCH node, one for the matched object and one for unmatched object.
The unmatched object set is needed if a superior node is a NOT that reverts the evaluation. The BOOLEAN nodes mix the sets
returned by the MATCH nodes"""
node.matched = set()
node.unmatched = set()
if node.elements:
for element in node.elements:
self.evaluate_filter_node(element, candidates)
if node.tag == ROOT:
return node.elements[0].matched
elif node.tag == AND:
first_element = node.elements[0]
node.matched.update(first_element.matched)
node.unmatched.update(first_element.unmatched)
for element in node.elements[1:]:
node.matched.intersection_update(element.matched)
node.unmatched.intersection_update(element.unmatched)
elif node.tag == OR:
for element in node.elements:
node.matched.update(element.matched)
node.unmatched.update(element.unmatched)
elif node.tag == NOT:
node.matched = node.elements[0].unmatched
node.unmatched = node.elements[0].matched
elif node.tag == MATCH_GREATER_OR_EQUAL:
attr_name = node.assertion['attr']
attr_value = node.assertion['value']
for candidate in candidates:
if attr_name in self.connection.server.dit[candidate]:
for value in self.connection.server.dit[candidate][attr_name]:
if value.isdigit() and attr_value.isdigit(): # int comparison
if int(value) >= int(attr_value):
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
else:
if to_unicode(value, SERVER_ENCODING).lower() >= to_unicode(attr_value, SERVER_ENCODING).lower(): # case insensitive string comparison
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
elif node.tag == MATCH_LESS_OR_EQUAL:
attr_name = node.assertion['attr']
attr_value = node.assertion['value']
for candidate in candidates:
if attr_name in self.connection.server.dit[candidate]:
for value in self.connection.server.dit[candidate][attr_name]:
if value.isdigit() and attr_value.isdigit(): # int comparison
if int(value) <= int(attr_value):
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
else:
if to_unicode(value, SERVER_ENCODING).lower() <= to_unicode(attr_value, SERVER_ENCODING).lower(): # case insentive string comparison
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
elif node.tag == MATCH_EXTENSIBLE:
self.connection.last_error = 'Extensible match not allowed in Mock strategy'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPDefinitionError(self.connection.last_error)
elif node.tag == MATCH_PRESENT:
attr_name = node.assertion['attr']
for candidate in candidates:
if attr_name in self.connection.server.dit[candidate]:
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
elif node.tag == MATCH_SUBSTRING:
attr_name = node.assertion['attr']
# rebuild the original substring filter
if 'initial' in node.assertion and node.assertion['initial'] is not None:
substring_filter = re.escape(to_unicode(node.assertion['initial'], SERVER_ENCODING))
else:
substring_filter = ''
if 'any' in node.assertion and node.assertion['any'] is not None:
for middle in node.assertion['any']:
substring_filter += '.*' + re.escape(to_unicode(middle, SERVER_ENCODING))
if 'final' in node.assertion and node.assertion['final'] is not None:
substring_filter += '.*' + re.escape(to_unicode(node.assertion['final'], SERVER_ENCODING))
if substring_filter and not node.assertion.get('any', None) and not node.assertion.get('final', None): # only initial, adds .*
substring_filter += '.*'
regex_filter = re.compile(substring_filter, flags=re.UNICODE | re.IGNORECASE) # unicode AND ignorecase
for candidate in candidates:
if attr_name in self.connection.server.dit[candidate]:
for value in self.connection.server.dit[candidate][attr_name]:
if regex_filter.match(to_unicode(value, SERVER_ENCODING)):
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
else:
node.unmatched.add(candidate)
elif node.tag == MATCH_EQUAL or node.tag == MATCH_APPROX:
attr_name = node.assertion['attr']
attr_value = node.assertion['value']
for candidate in candidates:
# if attr_name in self.connection.server.dit[candidate] and attr_value in self.connection.server.dit[candidate][attr_name]:
if attr_name in self.connection.server.dit[candidate] and self.equal(candidate, attr_name, attr_value):
node.matched.add(candidate)
else:
node.unmatched.add(candidate)
def equal(self, dn, attribute_type, value_to_check):
# value is the value to match
attribute_values = self.connection.server.dit[dn][attribute_type]
if not isinstance(attribute_values, SEQUENCE_TYPES):
attribute_values = [attribute_values]
escaped_value_to_check = ldap_escape_to_bytes(value_to_check)
for attribute_value in attribute_values:
if self._check_equality(escaped_value_to_check, attribute_value):
return True
if self._check_equality(self._prepare_value(attribute_type, value_to_check), attribute_value):
return True
return False
@staticmethod
def _check_equality(value1, value2):
if value1 == value2: # exact matching
return True
if str(value1).isdigit() and str(value2).isdigit():
if int(value1) == int(value2): # int comparison
return True
try:
if to_unicode(value1, SERVER_ENCODING).lower() == to_unicode(value2, SERVER_ENCODING).lower(): # case insensitive comparison
return True
except UnicodeError:
pass
return False
def send(self, message_type, request, controls=None):
self.connection.request = self.decode_request(message_type, request, controls)
if self.connection.listening:
message_id = self.connection.server.next_message_id()
if self.connection.usage: # ldap message is built for updating metrics only
ldap_message = LDAPMessage()
ldap_message['messageID'] = MessageID(message_id)
ldap_message['protocolOp'] = ProtocolOp().setComponentByName(message_type, request)
message_controls = build_controls_list(controls)
if message_controls is not None:
ldap_message['controls'] = message_controls
asn1_request = BaseStrategy.decode_request(message_type, request, controls)
self.connection._usage.update_transmitted_message(asn1_request, len(encode(ldap_message)))
return message_id, message_type, request, controls
else:
self.connection.last_error = 'unable to send message, connection is not open'
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise LDAPSocketOpenError(self.connection.last_error)