codekingpro/portable-devtools
114k
1"""2"""3 4# Created on 2014.05.315#6# Author: Giovanni Cannata7#8# Copyright 2014 - 2020 Giovanni Cannata9#10# This file is part of ldap3.11#12# ldap3 is free software: you can redistribute it and/or modify13# it under the terms of the GNU Lesser General Public License as published14# by the Free Software Foundation, either version 3 of the License, or15# (at your option) any later version.16#17# ldap3 is distributed in the hope that it will be useful,18# but WITHOUT ANY WARRANTY; without even the implied warranty of19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the20# GNU Lesser General Public License for more details.21#22# You should have received a copy of the GNU Lesser General Public License23# along with ldap3 in the COPYING and COPYING.LESSER files.24# If not, see <http://www.gnu.org/licenses/>.25from copy import deepcopy, copy26from os import linesep27from threading import RLock28from functools import reduce29import json30 31from .. import ANONYMOUS, SIMPLE, SASL, MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE, get_config_parameter, DEREF_ALWAYS, \32 SUBTREE, ASYNC, SYNC, NO_ATTRIBUTES, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, MODIFY_INCREMENT, LDIF, ASYNC_STREAM, \33 RESTARTABLE, ROUND_ROBIN, REUSABLE, AUTO_BIND_DEFAULT, AUTO_BIND_NONE, AUTO_BIND_TLS_BEFORE_BIND, SAFE_SYNC, SAFE_RESTARTABLE, \34 AUTO_BIND_TLS_AFTER_BIND, AUTO_BIND_NO_TLS, STRING_TYPES, SEQUENCE_TYPES, MOCK_SYNC, MOCK_ASYNC, NTLM, EXTERNAL,\35 DIGEST_MD5, GSSAPI, PLAIN, DSA, SCHEMA, ALL36 37from .results import RESULT_SUCCESS, RESULT_COMPARE_TRUE, RESULT_COMPARE_FALSE38from ..extend import ExtendedOperationsRoot39from .pooling import ServerPool40from .server import Server41from ..operation.abandon import abandon_operation, abandon_request_to_dict42from ..operation.add import add_operation, add_request_to_dict43from ..operation.bind import bind_operation, bind_request_to_dict44from ..operation.compare import compare_operation, compare_request_to_dict45from ..operation.delete import delete_operation, delete_request_to_dict46from ..operation.extended import extended_operation, extended_request_to_dict47from ..operation.modify import modify_operation, modify_request_to_dict48from ..operation.modifyDn import modify_dn_operation, modify_dn_request_to_dict49from ..operation.search import search_operation, search_request_to_dict50from ..protocol.rfc2849 import operation_to_ldif, add_ldif_header51from ..protocol.sasl.digestMd5 import sasl_digest_md552from ..protocol.sasl.external import sasl_external53from ..protocol.sasl.plain import sasl_plain54from ..strategy.sync import SyncStrategy55from ..strategy.safeSync import SafeSyncStrategy56from ..strategy.safeRestartable import SafeRestartableStrategy57from ..strategy.mockAsync import MockAsyncStrategy58from ..strategy.asynchronous import AsyncStrategy59from ..strategy.reusable import ReusableStrategy60from ..strategy.restartable import RestartableStrategy61from ..strategy.ldifProducer import LdifProducerStrategy62from ..strategy.mockSync import MockSyncStrategy63from ..strategy.asyncStream import AsyncStreamStrategy64from ..operation.unbind import unbind_operation65from ..protocol.rfc2696 import paged_search_control66from .usage import ConnectionUsage67from .tls import Tls68from .exceptions import LDAPUnknownStrategyError, LDAPBindError, LDAPUnknownAuthenticationMethodError, \69 LDAPSASLMechanismNotSupportedError, LDAPObjectClassError, LDAPConnectionIsReadOnlyError, LDAPChangeError, LDAPExceptionError, \70 LDAPObjectError, LDAPSocketReceiveError, LDAPAttributeError, LDAPInvalidValueError, LDAPInvalidPortError, LDAPStartTLSError71 72from ..utils.conv import escape_bytes, prepare_for_stream, check_json_dict, format_json, to_unicode73from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED, get_library_log_hide_sensitive_data74from ..utils.dn import safe_dn75from ..utils.port_validators import check_port_and_port_list76 77 78SASL_AVAILABLE_MECHANISMS = [EXTERNAL,79 DIGEST_MD5,80 GSSAPI,81 PLAIN]82 83CLIENT_STRATEGIES = [SYNC,84 SAFE_SYNC,85 SAFE_RESTARTABLE,86 ASYNC,87 LDIF,88 RESTARTABLE,89 REUSABLE,90 MOCK_SYNC,91 MOCK_ASYNC,92 ASYNC_STREAM]93 94 95def _format_socket_endpoint(endpoint):96 if endpoint and len(endpoint) == 2: # IPv497 return str(endpoint[0]) + ':' + str(endpoint[1])98 elif endpoint and len(endpoint) == 4: # IPv699 return '[' + str(endpoint[0]) + ']:' + str(endpoint[1])100 101 try:102 return str(endpoint)103 except Exception:104 return '?'105 106 107def _format_socket_endpoints(sock):108 if sock:109 try:110 local = sock.getsockname()111 except Exception:112 local = (None, None, None, None)113 try:114 remote = sock.getpeername()115 except Exception:116 remote = (None, None, None, None)117 118 return '<local: ' + _format_socket_endpoint(local) + ' - remote: ' + _format_socket_endpoint(remote) + '>'119 return '<no socket>'120 121 122class Connection(object):123 """Main ldap connection class.124 125 Controls, if used, must be a list of tuples. Each tuple must have 3126 elements, the control OID, a boolean meaning if the control is127 critical, a value.128 129 If the boolean is set to True the server must honor the control or130 refuse the operation131 132 Mixing controls must be defined in controls specification (as per133 RFC 4511)134 135 :param server: the Server object to connect to136 :type server: Server, str137 :param user: the user name for simple authentication138 :type user: str139 :param password: the password for simple authentication140 :type password: str141 :param auto_bind: specify if the bind will be performed automatically when defining the Connection object142 :type auto_bind: int, can be one of AUTO_BIND_DEFAULT, AUTO_BIND_NONE, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND, AUTO_BIND_TLS_AFTER_BIND as specified in ldap3143 :param version: LDAP version, default to 3144 :type version: int145 :param authentication: type of authentication146 :type authentication: int, can be one of ANONYMOUS, SIMPLE or SASL, as specified in ldap3147 :param client_strategy: communication strategy used in the Connection148 :type client_strategy: can be one of SYNC, ASYNC, LDIF, RESTARTABLE, REUSABLE as specified in ldap3149 :param auto_referrals: specify if the connection object must automatically follow referrals150 :type auto_referrals: bool151 :param sasl_mechanism: mechanism for SASL authentication, can be one of 'EXTERNAL', 'DIGEST-MD5', 'GSSAPI', 'PLAIN'152 :type sasl_mechanism: str153 :param sasl_credentials: credentials for SASL mechanism154 :type sasl_credentials: tuple155 :param check_names: if True the library will check names of attributes and object classes against the schema. Also values found in entries will be formatted as indicated by the schema156 :type check_names: bool157 :param collect_usage: collect usage metrics in the usage attribute158 :type collect_usage: bool159 :param read_only: disable operations that modify data in the LDAP server160 :type read_only: bool161 :param lazy: open and bind the connection only when an actual operation is performed162 :type lazy: bool163 :param raise_exceptions: raise exceptions when operations are not successful, if False operations return False if not successful but not raise exceptions164 :type raise_exceptions: bool165 :param pool_name: pool name for pooled strategies166 :type pool_name: str167 :param pool_size: pool size for pooled strategies168 :type pool_size: int169 :param pool_lifetime: pool lifetime for pooled strategies170 :type pool_lifetime: int171 :param cred_store: credential store for gssapi172 :type cred_store: dict173 :param use_referral_cache: keep referral connections open and reuse them174 :type use_referral_cache: bool175 :param auto_escape: automatic escaping of filter values176 :type auto_escape: bool177 :param auto_encode: automatic encoding of attribute values178 :type auto_encode: bool179 :param source_address: the ip address or hostname to use as the source when opening the connection to the server180 :type source_address: str181 :param source_port: the source port to use when opening the connection to the server. Cannot be specified with source_port_list182 :type source_port: int183 :param source_port_list: a list of source ports to choose from when opening the connection to the server. Cannot be specified with source_port184 :type source_port_list: list185 """186 def __init__(self,187 server,188 user=None,189 password=None,190 auto_bind=AUTO_BIND_DEFAULT,191 version=3,192 authentication=None,193 client_strategy=SYNC,194 auto_referrals=True,195 auto_range=True,196 sasl_mechanism=None,197 sasl_credentials=None,198 check_names=True,199 collect_usage=False,200 read_only=False,201 lazy=False,202 raise_exceptions=False,203 pool_name=None,204 pool_size=None,205 pool_lifetime=None,206 cred_store=None,207 fast_decoder=True,208 receive_timeout=None,209 return_empty_attributes=True,210 use_referral_cache=False,211 auto_escape=True,212 auto_encode=True,213 pool_keepalive=None,214 source_address=None,215 source_port=None,216 source_port_list=None):217 218 conf_default_pool_name = get_config_parameter('DEFAULT_THREADED_POOL_NAME')219 self.connection_lock = RLock() # re-entrant lock to ensure that operations in the Connection object are executed atomically in the same thread220 with self.connection_lock:221 if client_strategy not in CLIENT_STRATEGIES:222 self.last_error = 'unknown client connection strategy'223 if log_enabled(ERROR):224 log(ERROR, '%s for <%s>', self.last_error, self)225 raise LDAPUnknownStrategyError(self.last_error)226 227 self.strategy_type = client_strategy228 self.user = user229 self.password = password230 231 if not authentication and self.user:232 self.authentication = SIMPLE233 elif not authentication:234 self.authentication = ANONYMOUS235 elif authentication in [SIMPLE, ANONYMOUS, SASL, NTLM]:236 self.authentication = authentication237 else:238 self.last_error = 'unknown authentication method'239 if log_enabled(ERROR):240 log(ERROR, '%s for <%s>', self.last_error, self)241 raise LDAPUnknownAuthenticationMethodError(self.last_error)242 243 self.version = version244 self.auto_referrals = True if auto_referrals else False245 self.request = None246 self.response = None247 self.result = None248 self.bound = False249 self.listening = False250 self.closed = True251 self.last_error = None252 if auto_bind is False: # compatibility with older version where auto_bind was a boolean253 self.auto_bind = AUTO_BIND_DEFAULT254 elif auto_bind is True:255 self.auto_bind = AUTO_BIND_NO_TLS256 else:257 self.auto_bind = auto_bind258 self.sasl_mechanism = sasl_mechanism259 self.sasl_credentials = sasl_credentials260 self._usage = ConnectionUsage() if collect_usage else None261 self.socket = None262 self.tls_started = False263 self.sasl_in_progress = False264 self.read_only = read_only265 self._context_state = []266 self._deferred_open = False267 self._deferred_bind = False268 self._deferred_start_tls = False269 self._bind_controls = None270 self._executing_deferred = False271 self.lazy = lazy272 self.pool_name = pool_name if pool_name else conf_default_pool_name273 self.pool_size = pool_size274 self.cred_store = cred_store275 self.pool_lifetime = pool_lifetime276 self.pool_keepalive = pool_keepalive277 self.starting_tls = False278 self.check_names = check_names279 self.raise_exceptions = raise_exceptions280 self.auto_range = True if auto_range else False281 self.extend = ExtendedOperationsRoot(self)282 self._entries = []283 self.fast_decoder = fast_decoder284 self.receive_timeout = receive_timeout285 self.empty_attributes = return_empty_attributes286 self.use_referral_cache = use_referral_cache287 self.auto_escape = auto_escape288 self.auto_encode = auto_encode289 self._digest_md5_kic = None290 self._digest_md5_kis = None291 self._digest_md5_sec_num = 0292 293 port_err = check_port_and_port_list(source_port, source_port_list)294 if port_err:295 if log_enabled(ERROR):296 log(ERROR, port_err)297 raise LDAPInvalidPortError(port_err)298 # using an empty string to bind a socket means "use the default as if this wasn't provided" because socket299 # binding requires that you pass something for the ip if you want to pass a specific port300 self.source_address = source_address if source_address is not None else ''301 # using 0 as the source port to bind a socket means "use the default behavior of picking a random port from302 # all ports as if this wasn't provided" because socket binding requires that you pass something for the port303 # if you want to pass a specific ip304 self.source_port_list = [0]305 if source_port is not None:306 self.source_port_list = [source_port]307 elif source_port_list is not None:308 self.source_port_list = source_port_list[:]309 310 if isinstance(server, STRING_TYPES):311 server = Server(server)312 if isinstance(server, SEQUENCE_TYPES):313 server = ServerPool(server, ROUND_ROBIN, active=True, exhaust=True)314 315 if isinstance(server, ServerPool):316 self.server_pool = server317 self.server_pool.initialize(self)318 self.server = self.server_pool.get_current_server(self)319 else:320 self.server_pool = None321 self.server = server322 323 # if self.authentication == SIMPLE and self.user and self.check_names:324 # self.user = safe_dn(self.user)325 # if log_enabled(EXTENDED):326 # log(EXTENDED, 'user name sanitized to <%s> for simple authentication via <%s>', self.user, self)327 328 if self.strategy_type == SYNC:329 self.strategy = SyncStrategy(self)330 elif self.strategy_type == SAFE_SYNC:331 self.strategy = SafeSyncStrategy(self)332 elif self.strategy_type == SAFE_RESTARTABLE:333 self.strategy = SafeRestartableStrategy(self)334 elif self.strategy_type == ASYNC:335 self.strategy = AsyncStrategy(self)336 elif self.strategy_type == LDIF:337 self.strategy = LdifProducerStrategy(self)338 elif self.strategy_type == RESTARTABLE:339 self.strategy = RestartableStrategy(self)340 elif self.strategy_type == REUSABLE:341 self.strategy = ReusableStrategy(self)342 self.lazy = False343 elif self.strategy_type == MOCK_SYNC:344 self.strategy = MockSyncStrategy(self)345 elif self.strategy_type == MOCK_ASYNC:346 self.strategy = MockAsyncStrategy(self)347 elif self.strategy_type == ASYNC_STREAM:348 self.strategy = AsyncStreamStrategy(self)349 else:350 self.last_error = 'unknown strategy'351 if log_enabled(ERROR):352 log(ERROR, '%s for <%s>', self.last_error, self)353 raise LDAPUnknownStrategyError(self.last_error)354 355 # maps strategy functions to connection functions356 self.send = self.strategy.send357 self.open = self.strategy.open358 self.get_response = self.strategy.get_response359 self.post_send_single_response = self.strategy.post_send_single_response360 self.post_send_search = self.strategy.post_send_search361 362 if not self.strategy.no_real_dsa:363 self._do_auto_bind()364 # else: # for strategies with a fake server set get_info to NONE if server hasn't a schema365 # if self.server and not self.server.schema:366 # self.server.get_info = NONE367 if log_enabled(BASIC):368 if get_library_log_hide_sensitive_data():369 log(BASIC, 'instantiated Connection: <%s>', self.repr_with_sensitive_data_stripped())370 else:371 log(BASIC, 'instantiated Connection: <%r>', self)372 373 def _prepare_return_value(self, status, response=False):374 if self.strategy.thread_safe:375 temp_response = self.response376 self.response = None377 temp_request = self.request378 self.request = None379 return status, deepcopy(self.result), deepcopy(temp_response) if response else None, copy(temp_request)380 return status381 382 def _do_auto_bind(self):383 if self.auto_bind and self.auto_bind not in [AUTO_BIND_NONE, AUTO_BIND_DEFAULT]:384 if log_enabled(BASIC):385 log(BASIC, 'performing automatic bind for <%s>', self)386 if self.closed:387 self.open(read_server_info=False)388 if self.auto_bind == AUTO_BIND_NO_TLS:389 self.bind(read_server_info=True)390 elif self.auto_bind == AUTO_BIND_TLS_BEFORE_BIND:391 if self.start_tls(read_server_info=False):392 self.bind(read_server_info=True)393 else:394 error = 'automatic start_tls befored bind not successful' + (' - ' + self.last_error if self.last_error else '')395 if log_enabled(ERROR):396 log(ERROR, '%s for <%s>', error, self)397 self.unbind() # unbind anyway to close connection398 raise LDAPStartTLSError(error)399 elif self.auto_bind == AUTO_BIND_TLS_AFTER_BIND:400 self.bind(read_server_info=False)401 if not self.start_tls(read_server_info=True):402 error = 'automatic start_tls after bind not successful' + (' - ' + self.last_error if self.last_error else '')403 if log_enabled(ERROR):404 log(ERROR, '%s for <%s>', error, self)405 self.unbind()406 raise LDAPStartTLSError(error)407 if not self.bound:408 error = 'automatic bind not successful' + (' - ' + self.last_error if self.last_error else '')409 if log_enabled(ERROR):410 log(ERROR, '%s for <%s>', error, self)411 self.unbind()412 raise LDAPBindError(error)413 414 def __str__(self):415 s = [416 str(self.server) if self.server else 'None',417 'user: ' + str(self.user),418 'lazy' if self.lazy else 'not lazy',419 'unbound' if not self.bound else ('deferred bind' if self._deferred_bind else 'bound'),420 'closed' if self.closed else ('deferred open' if self._deferred_open else 'open'),421 _format_socket_endpoints(self.socket),422 'tls not started' if not self.tls_started else('deferred start_tls' if self._deferred_start_tls else 'tls started'),423 'listening' if self.listening else 'not listening',424 self.strategy.__class__.__name__ if hasattr(self, 'strategy') else 'No strategy',425 'internal decoder' if self.fast_decoder else 'pyasn1 decoder'426 ]427 return ' - '.join(s)428 429 def __repr__(self):430 conf_default_pool_name = get_config_parameter('DEFAULT_THREADED_POOL_NAME')431 if self.server_pool:432 r = 'Connection(server={0.server_pool!r}'.format(self)433 else:434 r = 'Connection(server={0.server!r}'.format(self)435 r += '' if self.user is None else ', user={0.user!r}'.format(self)436 r += '' if self.password is None else ', password={0.password!r}'.format(self)437 r += '' if self.auto_bind is None else ', auto_bind={0.auto_bind!r}'.format(self)438 r += '' if self.version is None else ', version={0.version!r}'.format(self)439 r += '' if self.authentication is None else ', authentication={0.authentication!r}'.format(self)440 r += '' if self.strategy_type is None else ', client_strategy={0.strategy_type!r}'.format(self)441 r += '' if self.auto_referrals is None else ', auto_referrals={0.auto_referrals!r}'.format(self)442 r += '' if self.sasl_mechanism is None else ', sasl_mechanism={0.sasl_mechanism!r}'.format(self)443 r += '' if self.sasl_credentials is None else ', sasl_credentials={0.sasl_credentials!r}'.format(self)444 r += '' if self.check_names is None else ', check_names={0.check_names!r}'.format(self)445 r += '' if self.usage is None else (', collect_usage=' + ('True' if self.usage else 'False'))446 r += '' if self.read_only is None else ', read_only={0.read_only!r}'.format(self)447 r += '' if self.lazy is None else ', lazy={0.lazy!r}'.format(self)448 r += '' if self.raise_exceptions is None else ', raise_exceptions={0.raise_exceptions!r}'.format(self)449 r += '' if (self.pool_name is None or self.pool_name == conf_default_pool_name) else ', pool_name={0.pool_name!r}'.format(self)450 r += '' if self.pool_size is None else ', pool_size={0.pool_size!r}'.format(self)451 r += '' if self.pool_lifetime is None else ', pool_lifetime={0.pool_lifetime!r}'.format(self)452 r += '' if self.pool_keepalive is None else ', pool_keepalive={0.pool_keepalive!r}'.format(self)453 r += '' if self.cred_store is None else (', cred_store=' + repr(self.cred_store))454 r += '' if self.fast_decoder is None else (', fast_decoder=' + ('True' if self.fast_decoder else 'False'))455 r += '' if self.auto_range is None else (', auto_range=' + ('True' if self.auto_range else 'False'))456 r += '' if self.receive_timeout is None else ', receive_timeout={0.receive_timeout!r}'.format(self)457 r += '' if self.empty_attributes is None else (', return_empty_attributes=' + ('True' if self.empty_attributes else 'False'))458 r += '' if self.auto_encode is None else (', auto_encode=' + ('True' if self.auto_encode else 'False'))459 r += '' if self.auto_escape is None else (', auto_escape=' + ('True' if self.auto_escape else 'False'))460 r += '' if self.use_referral_cache is None else (', use_referral_cache=' + ('True' if self.use_referral_cache else 'False'))461 r += ')'462 463 return r464 465 def repr_with_sensitive_data_stripped(self):466 conf_default_pool_name = get_config_parameter('DEFAULT_THREADED_POOL_NAME')467 if self.server_pool:468 r = 'Connection(server={0.server_pool!r}'.format(self)469 else:470 r = 'Connection(server={0.server!r}'.format(self)471 r += '' if self.user is None else ', user={0.user!r}'.format(self)472 r += '' if self.password is None else ", password='{0}'".format('<stripped %d characters of sensitive data>' % len(self.password))473 r += '' if self.auto_bind is None else ', auto_bind={0.auto_bind!r}'.format(self)474 r += '' if self.version is None else ', version={0.version!r}'.format(self)475 r += '' if self.authentication is None else ', authentication={0.authentication!r}'.format(self)476 r += '' if self.strategy_type is None else ', client_strategy={0.strategy_type!r}'.format(self)477 r += '' if self.auto_referrals is None else ', auto_referrals={0.auto_referrals!r}'.format(self)478 r += '' if self.sasl_mechanism is None else ', sasl_mechanism={0.sasl_mechanism!r}'.format(self)479 if self.sasl_mechanism == DIGEST_MD5:480 r += '' if self.sasl_credentials is None else ", sasl_credentials=({0!r}, {1!r}, '{2}', {3!r})".format(self.sasl_credentials[0], self.sasl_credentials[1], '*' * len(self.sasl_credentials[2]), self.sasl_credentials[3])481 else:482 r += '' if self.sasl_credentials is None else ', sasl_credentials={0.sasl_credentials!r}'.format(self)483 r += '' if self.check_names is None else ', check_names={0.check_names!r}'.format(self)484 r += '' if self.usage is None else (', collect_usage=' + 'True' if self.usage else 'False')485 r += '' if self.read_only is None else ', read_only={0.read_only!r}'.format(self)486 r += '' if self.lazy is None else ', lazy={0.lazy!r}'.format(self)487 r += '' if self.raise_exceptions is None else ', raise_exceptions={0.raise_exceptions!r}'.format(self)488 r += '' if (self.pool_name is None or self.pool_name == conf_default_pool_name) else ', pool_name={0.pool_name!r}'.format(self)489 r += '' if self.pool_size is None else ', pool_size={0.pool_size!r}'.format(self)490 r += '' if self.pool_lifetime is None else ', pool_lifetime={0.pool_lifetime!r}'.format(self)491 r += '' if self.pool_keepalive is None else ', pool_keepalive={0.pool_keepalive!r}'.format(self)492 r += '' if self.cred_store is None else (', cred_store=' + repr(self.cred_store))493 r += '' if self.fast_decoder is None else (', fast_decoder=' + 'True' if self.fast_decoder else 'False')494 r += '' if self.auto_range is None else (', auto_range=' + ('True' if self.auto_range else 'False'))495 r += '' if self.receive_timeout is None else ', receive_timeout={0.receive_timeout!r}'.format(self)496 r += '' if self.empty_attributes is None else (', return_empty_attributes=' + 'True' if self.empty_attributes else 'False')497 r += '' if self.auto_encode is None else (', auto_encode=' + ('True' if self.auto_encode else 'False'))498 r += '' if self.auto_escape is None else (', auto_escape=' + ('True' if self.auto_escape else 'False'))499 r += '' if self.use_referral_cache is None else (', use_referral_cache=' + ('True' if self.use_referral_cache else 'False'))500 r += ')'501 502 return r503 504 @property505 def stream(self):506 """Used by the LDIFProducer strategy to accumulate the ldif-change operations with a single LDIF header507 :return: reference to the response stream if defined in the strategy.508 """509 return self.strategy.get_stream() if self.strategy.can_stream else None510 511 @stream.setter512 def stream(self, value):513 with self.connection_lock:514 if self.strategy.can_stream:515 self.strategy.set_stream(value)516 517 @property518 def usage(self):519 """Usage statistics for the connection.520 :return: Usage object521 """522 if not self._usage:523 return None524 if self.strategy.pooled: # update master connection usage from pooled connections525 self._usage.reset()526 for worker in self.strategy.pool.workers:527 self._usage += worker.connection.usage528 self._usage += self.strategy.pool.terminated_usage529 return self._usage530 531 def __enter__(self):532 with self.connection_lock:533 self._context_state.append((self.bound, self.closed)) # save status out of context as a tuple in a list534 if self.auto_bind != AUTO_BIND_NONE:535 if self.auto_bind == AUTO_BIND_DEFAULT:536 self.auto_bind = AUTO_BIND_NO_TLS537 if self.closed:538 self.open()539 if not self.bound:540 if not self.bind():541 raise LDAPBindError('unable to bind')542 543 return self544 545 def __exit__(self, exc_type, exc_val, exc_tb):546 with self.connection_lock:547 context_bound, context_closed = self._context_state.pop()548 if (not context_bound and self.bound) or self.stream: # restore status prior to entering context549 try:550 self.unbind()551 except LDAPExceptionError:552 pass553 554 if not context_closed and self.closed:555 self.open()556 557 if exc_type is not None:558 if log_enabled(ERROR):559 log(ERROR, '%s for <%s>', exc_type, self)560 return False # re-raise LDAPExceptionError561 562 def bind(self,563 read_server_info=True,564 controls=None):565 """Bind to ldap Server with the authentication method and the user defined in the connection566 567 :param read_server_info: reads info from server568 :param controls: LDAP controls to send along with the bind operation569 :type controls: list of tuple570 :return: bool571 572 """573 if log_enabled(BASIC):574 log(BASIC, 'start BIND operation via <%s>', self)575 self.last_error = None576 with self.connection_lock:577 if self.lazy and not self._executing_deferred:578 if self.strategy.pooled:579 self.strategy.validate_bind(controls)580 self._deferred_bind = True581 self._bind_controls = controls582 self.bound = True583 if log_enabled(BASIC):584 log(BASIC, 'deferring bind for <%s>', self)585 else:586 self._deferred_bind = False587 self._bind_controls = None588 if self.closed: # try to open connection if closed589 self.open(read_server_info=False)590 if self.authentication == ANONYMOUS:591 if log_enabled(PROTOCOL):592 log(PROTOCOL, 'performing anonymous BIND for <%s>', self)593 if not self.strategy.pooled:594 request = bind_operation(self.version, self.authentication, self.user, '', auto_encode=self.auto_encode)595 if log_enabled(PROTOCOL):596 log(PROTOCOL, 'anonymous BIND request <%s> sent via <%s>', bind_request_to_dict(request), self)597 response = self.post_send_single_response(self.send('bindRequest', request, controls))598 else:599 response = self.strategy.validate_bind(controls) # only for REUSABLE600 elif self.authentication == SIMPLE:601 if log_enabled(PROTOCOL):602 log(PROTOCOL, 'performing simple BIND for <%s>', self)603 if not self.strategy.pooled:604 request = bind_operation(self.version, self.authentication, self.user, self.password, auto_encode=self.auto_encode)605 if log_enabled(PROTOCOL):606 log(PROTOCOL, 'simple BIND request <%s> sent via <%s>', bind_request_to_dict(request), self)607 response = self.post_send_single_response(self.send('bindRequest', request, controls))608 else:609 response = self.strategy.validate_bind(controls) # only for REUSABLE610 elif self.authentication == SASL:611 if self.sasl_mechanism in SASL_AVAILABLE_MECHANISMS:612 if log_enabled(PROTOCOL):613 log(PROTOCOL, 'performing SASL BIND for <%s>', self)614 if not self.strategy.pooled:615 response = self.do_sasl_bind(controls)616 else:617 response = self.strategy.validate_bind(controls) # only for REUSABLE618 else:619 self.last_error = 'requested SASL mechanism not supported'620 if log_enabled(ERROR):621 log(ERROR, '%s for <%s>', self.last_error, self)622 raise LDAPSASLMechanismNotSupportedError(self.last_error)623 elif self.authentication == NTLM:624 if self.user and self.password and len(self.user.split('\\')) == 2:625 if log_enabled(PROTOCOL):626 log(PROTOCOL, 'performing NTLM BIND for <%s>', self)627 if not self.strategy.pooled:628 response = self.do_ntlm_bind(controls)629 else:630 response = self.strategy.validate_bind(controls) # only for REUSABLE631 else: # user or password missing632 self.last_error = 'NTLM needs domain\\username and a password'633 if log_enabled(ERROR):634 log(ERROR, '%s for <%s>', self.last_error, self)635 raise LDAPUnknownAuthenticationMethodError(self.last_error)636 else:637 self.last_error = 'unknown authentication method'638 if log_enabled(ERROR):639 log(ERROR, '%s for <%s>', self.last_error, self)640 raise LDAPUnknownAuthenticationMethodError(self.last_error)641 642 if not self.strategy.sync and not self.strategy.pooled and self.authentication not in (SASL, NTLM): # get response if asynchronous except for SASL and NTLM that return the bind result even for asynchronous strategy643 _, result = self.get_response(response)644 if log_enabled(PROTOCOL):645 log(PROTOCOL, 'async BIND response id <%s> received via <%s>', result, self)646 elif self.strategy.sync:647 result = self.result648 if log_enabled(PROTOCOL):649 log(PROTOCOL, 'BIND response <%s> received via <%s>', result, self)650 elif self.strategy.pooled or self.authentication in (SASL, NTLM): # asynchronous SASL and NTLM or reusable strtegy get the bind result synchronously651 result = response652 else:653 self.last_error = 'unknown authentication method'654 if log_enabled(ERROR):655 log(ERROR, '%s for <%s>', self.last_error, self)656 raise LDAPUnknownAuthenticationMethodError(self.last_error)657 658 if result is None:659 # self.bound = True if self.strategy_type == REUSABLE else False660 self.bound = False661 elif result is True:662 self.bound = True663 elif result is False:664 self.bound = False665 else:666 self.bound = True if result['result'] == RESULT_SUCCESS else False667 if not self.bound and result and result['description'] and not self.last_error:668 self.last_error = result['description']669 670 if read_server_info and self.bound:671 self.refresh_server_info()672 self._entries = []673 674 if log_enabled(BASIC):675 log(BASIC, 'done BIND operation, result <%s>', self.bound)676 677 return self._prepare_return_value(self.bound, self.result)678 679 def rebind(self,680 user=None,681 password=None,682 authentication=None,683 sasl_mechanism=None,684 sasl_credentials=None,685 read_server_info=True,686 controls=None687 ):688 689 if log_enabled(BASIC):690 log(BASIC, 'start (RE)BIND operation via <%s>', self)691 self.last_error = None692 with self.connection_lock:693 if user:694 self.user = user695 if password is not None:696 self.password = password697 if not authentication and user:698 self.authentication = SIMPLE699 if authentication in [SIMPLE, ANONYMOUS, SASL, NTLM]:700 self.authentication = authentication701 elif authentication is not None:702 self.last_error = 'unknown authentication method'703 if log_enabled(ERROR):704 log(ERROR, '%s for <%s>', self.last_error, self)705 raise LDAPUnknownAuthenticationMethodError(self.last_error)706 if sasl_mechanism:707 self.sasl_mechanism = sasl_mechanism708 if sasl_credentials:709 self.sasl_credentials = sasl_credentials710 711 # if self.authentication == SIMPLE and self.user and self.check_names:712 # self.user = safe_dn(self.user)713 # if log_enabled(EXTENDED):714 # log(EXTENDED, 'user name sanitized to <%s> for rebind via <%s>', self.user, self)715 716 if not self.strategy.pooled:717 try:718 return self.bind(read_server_info, controls)719 except LDAPSocketReceiveError:720 self.last_error = 'Unable to rebind as a different user, furthermore the server abruptly closed the connection'721 if log_enabled(ERROR):722 log(ERROR, '%s for <%s>', self.last_error, self)723 raise LDAPBindError(self.last_error)724 else:725 self.strategy.pool.rebind_pool()726 return self._prepare_return_value(True, self.result)727 728 def unbind(self,729 controls=None):730 """Unbind the connected user. Unbind implies closing session as per RFC4511 (4.3)731 732 :param controls: LDAP controls to send along with the bind operation733 734 """735 if log_enabled(BASIC):736 log(BASIC, 'start UNBIND operation via <%s>', self)737 738 if self.use_referral_cache:739 self.strategy.unbind_referral_cache()740 741 self.last_error = None742 with self.connection_lock:743 if self.lazy and not self._executing_deferred and (self._deferred_bind or self._deferred_open): # _clear deferred status744 self.strategy.close()745 self._deferred_open = False746 self._deferred_bind = False747 self._deferred_start_tls = False748 elif not self.closed:749 request = unbind_operation()750 if log_enabled(PROTOCOL):751 log(PROTOCOL, 'UNBIND request sent via <%s>', self)752 self.send('unbindRequest', request, controls)753 self.strategy.close()754 755 if log_enabled(BASIC):756 log(BASIC, 'done UNBIND operation, result <%s>', True)757 758 return self._prepare_return_value(True)759 760 def search(self,761 search_base,762 search_filter,763 search_scope=SUBTREE,764 dereference_aliases=DEREF_ALWAYS,765 attributes=None,766 size_limit=0,767 time_limit=0,768 types_only=False,769 get_operational_attributes=False,770 controls=None,771 paged_size=None,772 paged_criticality=False,773 paged_cookie=None,774 auto_escape=None):775 """776 Perform an ldap search:777 778 - If attributes is empty noRFC2696 with the specified size779 - If paged is 0 and cookie is present the search is abandoned on780 server attribute is returned781 - If attributes is ALL_ATTRIBUTES all attributes are returned782 - If paged_size is an int greater than 0 a simple paged search783 is tried as described in784 - Cookie is an opaque string received in the last paged search785 and must be used on the next paged search response786 - If lazy == True open and bind will be deferred until another787 LDAP operation is performed788 - If mssing_attributes == True then an attribute not returned by the server is set to None789 - If auto_escape is set it overrides the Connection auto_escape790 """791 conf_attributes_excluded_from_check = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')]792 if log_enabled(BASIC):793 log(BASIC, 'start SEARCH operation via <%s>', self)794 795 if self.check_names and search_base:796 search_base = safe_dn(search_base)797 if log_enabled(EXTENDED):798 log(EXTENDED, 'search base sanitized to <%s> for SEARCH operation via <%s>', search_base, self)799 800 with self.connection_lock:801 self._fire_deferred()802 if not attributes:803 attributes = [NO_ATTRIBUTES]804 elif attributes == ALL_ATTRIBUTES:805 attributes = [ALL_ATTRIBUTES]806 807 if isinstance(attributes, STRING_TYPES):808 attributes = [attributes]809 810 if get_operational_attributes and isinstance(attributes, list):811 attributes.append(ALL_OPERATIONAL_ATTRIBUTES)812 elif get_operational_attributes and isinstance(attributes, tuple):813 attributes += (ALL_OPERATIONAL_ATTRIBUTES, ) # concatenate tuple814 815 if isinstance(paged_size, int):816 if log_enabled(PROTOCOL):817 log(PROTOCOL, 'performing paged search for %d items with cookie <%s> for <%s>', paged_size, escape_bytes(paged_cookie), self)818 819 if controls is None:820 controls = []821 else:822 # Copy the controls to prevent modifying the original object823 controls = list(controls)824 controls.append(paged_search_control(paged_criticality, paged_size, paged_cookie))825 826 if self.server and self.server.schema and self.check_names:827 for attribute_name in attributes:828 if ';' in attribute_name: # remove tags829 attribute_name_to_check = attribute_name.split(';')[0]830 else:831 attribute_name_to_check = attribute_name832 if self.server.schema and attribute_name_to_check.lower() not in conf_attributes_excluded_from_check and attribute_name_to_check not in self.server.schema.attribute_types:833 self.last_error = 'invalid attribute type ' + attribute_name_to_check834 if log_enabled(ERROR):835 log(ERROR, '%s for <%s>', self.last_error, self)836 raise LDAPAttributeError(self.last_error)837 838 request = search_operation(search_base,839 search_filter,840 search_scope,841 dereference_aliases,842 attributes,843 size_limit,844 time_limit,845 types_only,846 self.auto_escape if auto_escape is None else auto_escape,847 self.auto_encode,848 self.server.schema if self.server else None,849 validator=self.server.custom_validator,850 check_names=self.check_names)851 if log_enabled(PROTOCOL):852 log(PROTOCOL, 'SEARCH request <%s> sent via <%s>', search_request_to_dict(request), self)853 response = self.post_send_search(self.send('searchRequest', request, controls))854 self._entries = []855 856 if isinstance(response, int): # asynchronous strategy857 return_value = response858 if log_enabled(PROTOCOL):859 log(PROTOCOL, 'async SEARCH response id <%s> received via <%s>', return_value, self)860 else:861 return_value = True if self.result['type'] == 'searchResDone' and len(response) > 0 else False862 if not return_value and self.result['result'] not in [RESULT_SUCCESS] and not self.last_error:863 self.last_error = self.result['description']864 865 if log_enabled(PROTOCOL):866 for entry in response:867 if entry['type'] == 'searchResEntry':868 log(PROTOCOL, 'SEARCH response entry <%s> received via <%s>', entry, self)869 elif entry['type'] == 'searchResRef':870 log(PROTOCOL, 'SEARCH response reference <%s> received via <%s>', entry, self)871 872 if log_enabled(BASIC):873 log(BASIC, 'done SEARCH operation, result <%s>', return_value)874 875 return self._prepare_return_value(return_value, response=True)876 877 def compare(self,878 dn,879 attribute,880 value,881 controls=None):882 """883 Perform a compare operation884 """885 conf_attributes_excluded_from_check = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')]886 887 if log_enabled(BASIC):888 log(BASIC, 'start COMPARE operation via <%s>', self)889 self.last_error = None890 if self.check_names:891 dn = safe_dn(dn)892 if log_enabled(EXTENDED):893 log(EXTENDED, 'dn sanitized to <%s> for COMPARE operation via <%s>', dn, self)894 895 if self.server and self.server.schema and self.check_names:896 if ';' in attribute: # remove tags for checking897 attribute_name_to_check = attribute.split(';')[0]898 else:899 attribute_name_to_check = attribute900 901 if self.server.schema.attribute_types and attribute_name_to_check.lower() not in conf_attributes_excluded_from_check and attribute_name_to_check not in self.server.schema.attribute_types:902 self.last_error = 'invalid attribute type ' + attribute_name_to_check903 if log_enabled(ERROR):904 log(ERROR, '%s for <%s>', self.last_error, self)905 raise LDAPAttributeError(self.last_error)906 907 if isinstance(value, SEQUENCE_TYPES): # value can't be a sequence908 self.last_error = 'value cannot be a sequence'909 if log_enabled(ERROR):910 log(ERROR, '%s for <%s>', self.last_error, self)911 raise LDAPInvalidValueError(self.last_error)912 913 with self.connection_lock:914 self._fire_deferred()915 request = compare_operation(dn, attribute, value, self.auto_encode, self.server.schema if self.server else None, validator=self.server.custom_validator if self.server else None, check_names=self.check_names)916 if log_enabled(PROTOCOL):917 log(PROTOCOL, 'COMPARE request <%s> sent via <%s>', compare_request_to_dict(request), self)918 response = self.post_send_single_response(self.send('compareRequest', request, controls))919 self._entries = []920 if isinstance(response, int):921 return_value = response922 if log_enabled(PROTOCOL):923 log(PROTOCOL, 'async COMPARE response id <%s> received via <%s>', return_value, self)924 else:925 return_value = True if self.result['type'] == 'compareResponse' and self.result['result'] == RESULT_COMPARE_TRUE else False926 if not return_value and self.result['result'] not in [RESULT_COMPARE_TRUE, RESULT_COMPARE_FALSE] and not self.last_error:927 self.last_error = self.result['description']928 929 if log_enabled(PROTOCOL):930 log(PROTOCOL, 'COMPARE response <%s> received via <%s>', response, self)931 932 if log_enabled(BASIC):933 log(BASIC, 'done COMPARE operation, result <%s>', return_value)934 935 return self._prepare_return_value(return_value)936 937 def add(self,938 dn,939 object_class=None,940 attributes=None,941 controls=None):942 """943 Add dn to the DIT, object_class is None, a class name or a list944 of class names.945 946 Attributes is a dictionary in the form 'attr': 'val' or 'attr':947 ['val1', 'val2', ...] for multivalued attributes948 """949 conf_attributes_excluded_from_check = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')]950 conf_classes_excluded_from_check = [v.lower() for v in get_config_parameter('CLASSES_EXCLUDED_FROM_CHECK')]951 if log_enabled(BASIC):952 log(BASIC, 'start ADD operation via <%s>', self)953 self.last_error = None954 _attributes = deepcopy(attributes) # dict could change when adding objectClass values955 if self.check_names:956 dn = safe_dn(dn)957 if log_enabled(EXTENDED):958 log(EXTENDED, 'dn sanitized to <%s> for ADD operation via <%s>', dn, self)959 960 with self.connection_lock:961 self._fire_deferred()962 attr_object_class = []963 if object_class is None:964 parm_object_class = []965 else:966 parm_object_class = list(object_class) if isinstance(object_class, SEQUENCE_TYPES) else [object_class]967 968 object_class_attr_name = ''969 if _attributes:970 for attr in _attributes:971 if attr.lower() == 'objectclass':972 object_class_attr_name = attr973 attr_object_class = list(_attributes[object_class_attr_name]) if isinstance(_attributes[object_class_attr_name], SEQUENCE_TYPES) else [_attributes[object_class_attr_name]]974 break975 else:976 _attributes = dict()977 978 if not object_class_attr_name:979 object_class_attr_name = 'objectClass'980 981 attr_object_class = [to_unicode(object_class) for object_class in attr_object_class] # converts objectclass to unicode in case of bytes value982 _attributes[object_class_attr_name] = reduce(lambda x, y: x + [y] if y not in x else x, parm_object_class + attr_object_class, []) # remove duplicate ObjectClasses983 984 if not _attributes[object_class_attr_name]:985 self.last_error = 'objectClass attribute is mandatory'986 if log_enabled(ERROR):987 log(ERROR, '%s for <%s>', self.last_error, self)988 raise LDAPObjectClassError(self.last_error)989 990 if self.server and self.server.schema and self.check_names:991 for object_class_name in _attributes[object_class_attr_name]:992 if object_class_name.lower() not in conf_classes_excluded_from_check and object_class_name not in self.server.schema.object_classes:993 self.last_error = 'invalid object class ' + str(object_class_name)994 if log_enabled(ERROR):995 log(ERROR, '%s for <%s>', self.last_error, self)996 raise LDAPObjectClassError(self.last_error)997 998 for attribute_name in _attributes:999 if ';' in attribute_name: # remove tags for checking1000 attribute_name_to_check = attribute_name.split(';')[0]1001 else:1002 attribute_name_to_check = attribute_name1003 1004 if attribute_name_to_check.lower() not in conf_attributes_excluded_from_check and attribute_name_to_check not in self.server.schema.attribute_types:1005 self.last_error = 'invalid attribute type ' + attribute_name_to_check1006 if log_enabled(ERROR):1007 log(ERROR, '%s for <%s>', self.last_error, self)1008 raise LDAPAttributeError(self.last_error)1009 1010 request = add_operation(dn, _attributes, self.auto_encode, self.server.schema if self.server else None, validator=self.server.custom_validator if self.server else None, check_names=self.check_names)1011 if log_enabled(PROTOCOL):1012 log(PROTOCOL, 'ADD request <%s> sent via <%s>', add_request_to_dict(request), self)1013 response = self.post_send_single_response(self.send('addRequest', request, controls))1014 self._entries = []1015 1016 if isinstance(response, STRING_TYPES + (int, )):1017 return_value = response1018 if log_enabled(PROTOCOL):1019 log(PROTOCOL, 'async ADD response id <%s> received via <%s>', return_value, self)1020 else:1021 if log_enabled(PROTOCOL):1022 log(PROTOCOL, 'ADD response <%s> received via <%s>', response, self)1023 return_value = True if self.result['type'] == 'addResponse' and self.result['result'] == RESULT_SUCCESS else False1024 if not return_value and self.result['result'] not in [RESULT_SUCCESS] and not self.last_error:1025 self.last_error = self.result['description']1026 1027 if log_enabled(BASIC):1028 log(BASIC, 'done ADD operation, result <%s>', return_value)1029 1030 return self._prepare_return_value(return_value)1031 1032 def delete(self,1033 dn,1034 controls=None):1035 """1036 Delete the entry identified by the DN from the DIB.1037 """1038 if log_enabled(BASIC):1039 log(BASIC, 'start DELETE operation via <%s>', self)1040 self.last_error = None1041 if self.check_names:1042 dn = safe_dn(dn)1043 if log_enabled(EXTENDED):1044 log(EXTENDED, 'dn sanitized to <%s> for DELETE operation via <%s>', dn, self)1045 1046 with self.connection_lock:1047 self._fire_deferred()1048 if self.read_only:1049 self.last_error = 'connection is read-only'1050 if log_enabled(ERROR):1051 log(ERROR, '%s for <%s>', self.last_error, self)1052 raise LDAPConnectionIsReadOnlyError(self.last_error)1053 1054 request = delete_operation(dn)1055 if log_enabled(PROTOCOL):1056 log(PROTOCOL, 'DELETE request <%s> sent via <%s>', delete_request_to_dict(request), self)1057 response = self.post_send_single_response(self.send('delRequest', request, controls))1058 self._entries = []1059 1060 if isinstance(response, STRING_TYPES + (int, )):1061 return_value = response1062 if log_enabled(PROTOCOL):1063 log(PROTOCOL, 'async DELETE response id <%s> received via <%s>', return_value, self)1064 else:1065 if log_enabled(PROTOCOL):1066 log(PROTOCOL, 'DELETE response <%s> received via <%s>', response, self)1067 return_value = True if self.result['type'] == 'delResponse' and self.result['result'] == RESULT_SUCCESS else False1068 if not return_value and self.result['result'] not in [RESULT_SUCCESS] and not self.last_error:1069 self.last_error = self.result['description']1070 1071 if log_enabled(BASIC):1072 log(BASIC, 'done DELETE operation, result <%s>', return_value)1073 1074 return self._prepare_return_value(return_value)1075 1076 def modify(self,1077 dn,1078 changes,1079 controls=None):1080 """1081 Modify attributes of entry1082 1083 - changes is a dictionary in the form {'attribute1': change), 'attribute2': [change, change, ...], ...}1084 - change is (operation, [value1, value2, ...])1085 - operation is 0 (MODIFY_ADD), 1 (MODIFY_DELETE), 2 (MODIFY_REPLACE), 3 (MODIFY_INCREMENT)1086 """1087 conf_attributes_excluded_from_check = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')]1088 1089 if log_enabled(BASIC):1090 log(BASIC, 'start MODIFY operation via <%s>', self)1091 self.last_error = None1092 if self.check_names:1093 dn = safe_dn(dn)1094 if log_enabled(EXTENDED):1095 log(EXTENDED, 'dn sanitized to <%s> for MODIFY operation via <%s>', dn, self)1096 1097 with self.connection_lock:1098 self._fire_deferred()1099 if self.read_only:1100 self.last_error = 'connection is read-only'1101 if log_enabled(ERROR):1102 log(ERROR, '%s for <%s>', self.last_error, self)1103 raise LDAPConnectionIsReadOnlyError(self.last_error)1104 1105 if not isinstance(changes, dict):1106 self.last_error = 'changes must be a dictionary'1107 if log_enabled(ERROR):1108 log(ERROR, '%s for <%s>', self.last_error, self)1109 raise LDAPChangeError(self.last_error)1110 1111 if not changes:1112 self.last_error = 'no changes in modify request'1113 if log_enabled(ERROR):1114 log(ERROR, '%s for <%s>', self.last_error, self)1115 raise LDAPChangeError(self.last_error)1116 1117 changelist = dict()1118 for attribute_name in changes:1119 if self.server and self.server.schema and self.check_names:1120 if ';' in attribute_name: # remove tags for checking1121 attribute_name_to_check = attribute_name.split(';')[0]1122 else:1123 attribute_name_to_check = attribute_name1124 1125 if self.server.schema.attribute_types and attribute_name_to_check.lower() not in conf_attributes_excluded_from_check and attribute_name_to_check not in self.server.schema.attribute_types:1126 self.last_error = 'invalid attribute type ' + attribute_name_to_check1127 if log_enabled(ERROR):1128 log(ERROR, '%s for <%s>', self.last_error, self)1129 raise LDAPAttributeError(self.last_error)1130 change = changes[attribute_name]1131 if isinstance(change, SEQUENCE_TYPES) and change[0] in [MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE, MODIFY_INCREMENT, 0, 1, 2, 3]:1132 if len(change) != 2:1133 self.last_error = 'malformed change'1134 if log_enabled(ERROR):1135 log(ERROR, '%s for <%s>', self.last_error, self)1136 raise LDAPChangeError(self.last_error)1137 1138 changelist[attribute_name] = [change] # insert change in a list1139 else:1140 for change_operation in change:1141 if len(change_operation) != 2 or change_operation[0] not in [MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE, MODIFY_INCREMENT, 0, 1, 2, 3]:1142 self.last_error = 'invalid change list'1143 if log_enabled(ERROR):1144 log(ERROR, '%s for <%s>', self.last_error, self)1145 raise LDAPChangeError(self.last_error)1146 changelist[attribute_name] = change1147 request = modify_operation(dn, changelist, self.auto_encode, self.server.schema if self.server else None, validator=self.server.custom_validator if self.server else None, check_names=self.check_names)1148 if log_enabled(PROTOCOL):1149 log(PROTOCOL, 'MODIFY request <%s> sent via <%s>', modify_request_to_dict(request), self)1150 response = self.post_send_single_response(self.send('modifyRequest', request, controls))1151 self._entries = []1152 1153 if isinstance(response, STRING_TYPES + (int, )):1154 return_value = response1155 if log_enabled(PROTOCOL):1156 log(PROTOCOL, 'async MODIFY response id <%s> received via <%s>', return_value, self)1157 else:1158 if log_enabled(PROTOCOL):1159 log(PROTOCOL, 'MODIFY response <%s> received via <%s>', response, self)1160 return_value = True if self.result['type'] == 'modifyResponse' and self.result['result'] == RESULT_SUCCESS else False1161 if not return_value and self.result['result'] not in [RESULT_SUCCESS] and not self.last_error:1162 self.last_error = self.result['description']1163 1164 if log_enabled(BASIC):1165 log(BASIC, 'done MODIFY operation, result <%s>', return_value)1166 1167 return self._prepare_return_value(return_value)1168 1169 def modify_dn(self,1170 dn,1171 relative_dn,1172 delete_old_dn=True,1173 new_superior=None,1174 controls=None):1175 """1176 Modify DN of the entry or performs a move of the entry in the1177 DIT.1178 """1179 if log_enabled(BASIC):1180 log(BASIC, 'start MODIFY DN operation via <%s>', self)1181 self.last_error = None1182 if self.check_names:1183 dn = safe_dn(dn)1184 if log_enabled(EXTENDED):1185 log(EXTENDED, 'dn sanitized to <%s> for MODIFY DN operation via <%s>', dn, self)1186 relative_dn = safe_dn(relative_dn)1187 if log_enabled(EXTENDED):1188 log(EXTENDED, 'relative dn sanitized to <%s> for MODIFY DN operation via <%s>', relative_dn, self)1189 1190 with self.connection_lock:1191 self._fire_deferred()1192 if self.read_only:1193 self.last_error = 'connection is read-only'1194 if log_enabled(ERROR):1195 log(ERROR, '%s for <%s>', self.last_error, self)1196 raise LDAPConnectionIsReadOnlyError(self.last_error)1197 1198 # if new_superior and not dn.startswith(relative_dn): # as per RFC4511 (4.9)1199 # self.last_error = 'DN cannot change while performing moving'1200 # if log_enabled(ERROR):