Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
cursor.py913 linesDownload Raw Back to abstract
1"""2"""3 4# Created on 2014.01.065#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 collections import namedtuple26from copy import deepcopy27from datetime import datetime28from os import linesep29from time import sleep30 31from . import STATUS_VIRTUAL, STATUS_READ, STATUS_WRITABLE32from .. import SUBTREE, LEVEL, DEREF_ALWAYS, DEREF_NEVER, BASE, SEQUENCE_TYPES, STRING_TYPES, get_config_parameter33from ..abstract import STATUS_PENDING_CHANGES34from .attribute import Attribute, OperationalAttribute, WritableAttribute35from .attrDef import AttrDef36from .objectDef import ObjectDef37from .entry import Entry, WritableEntry38from ..core.exceptions import LDAPCursorError, LDAPObjectDereferenceError39from ..core.results import RESULT_SUCCESS40from ..utils.ciDict import CaseInsensitiveWithAliasDict41from ..utils.dn import safe_dn, safe_rdn42from ..utils.conv import to_raw43from ..utils.config import get_config_parameter44from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED45from ..protocol.oid import ATTRIBUTE_DIRECTORY_OPERATION, ATTRIBUTE_DISTRIBUTED_OPERATION, ATTRIBUTE_DSA_OPERATION, CLASS_AUXILIARY46 47Operation = namedtuple('Operation', ('request', 'result', 'response'))48 49 50def _ret_search_value(value):51    return value[0] + '=' + value[1:] if value[0] in '<>~' and value[1] != '=' else value52 53 54def _create_query_dict(query_text):55    """56    Create a dictionary with query key:value definitions57    query_text is a comma delimited key:value sequence58    """59    query_dict = dict()60    if query_text:61        for arg_value_str in query_text.split(','):62            if ':' in arg_value_str:63                arg_value_list = arg_value_str.split(':')64                query_dict[arg_value_list[0].strip()] = arg_value_list[1].strip()65 66    return query_dict67 68 69class Cursor(object):70    # entry_class and attribute_class define the type of entry and attribute used by the cursor71    # entry_initial_status defines the initial status of a entry72    # entry_class = Entry, must be defined in subclasses73    # attribute_class = Attribute, must be defined in subclasses74    # entry_initial_status = STATUS, must be defined in subclasses75 76    def __init__(self, connection, object_def, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):77        conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]78        self.connection = connection79        self.get_operational_attributes = get_operational_attributes80        if connection._deferred_bind or connection._deferred_open:  # probably a lazy connection, tries to bind81            connection._fire_deferred()82 83        if isinstance(object_def, (STRING_TYPES, SEQUENCE_TYPES)):84            if connection.closed:  # try to open connection if closed to read schema85                connection.bind()86            object_def = ObjectDef(object_def, connection.server.schema, auxiliary_class=auxiliary_class)87        self.definition = object_def88        if attributes:  # checks if requested attributes are defined in ObjectDef89            not_defined_attributes = []90            if isinstance(attributes, STRING_TYPES):91                attributes = [attributes]92 93            for attribute in attributes:94                if attribute not in self.definition._attributes and attribute.lower() not in conf_attributes_excluded_from_object_def:95                    not_defined_attributes.append(attribute)96 97            if not_defined_attributes:98                error_message = 'Attributes \'%s\' non in definition' % ', '.join(not_defined_attributes)99                if log_enabled(ERROR):100                    log(ERROR, '%s for <%s>', error_message, self)101                raise LDAPCursorError(error_message)102 103        self.attributes = set(attributes) if attributes else set([attr.name for attr in self.definition])104        self.controls = controls105        self.execution_time = None106        self.entries = []107        self.schema = self.connection.server.schema108        self._do_not_reset = False  # used for refreshing entry in entry_refresh() without removing all entries from the Cursor109        self._operation_history = list()  # a list storing all the requests, results and responses for the last cursor operation110 111    def __repr__(self):112        r = 'CURSOR : ' + self.__class__.__name__ + linesep113        r += 'CONN   : ' + str(self.connection) + linesep114        r += 'DEFS   : ' + ', '.join(self.definition._object_class)115        if self.definition._auxiliary_class:116            r += ' [AUX: ' + ', '.join(self.definition._auxiliary_class) + ']'117        r += linesep118        # for attr_def in sorted(self.definition):119        #     r += (attr_def.key if attr_def.key == attr_def.name else (attr_def.key + ' <' + attr_def.name + '>')) + ', '120        # if r[-2] == ',':121        #     r = r[:-2]122        # r += ']' + linesep123        if hasattr(self, 'attributes'):124            r += 'ATTRS  : ' + repr(sorted(self.attributes)) + (' [OPERATIONAL]' if self.get_operational_attributes else '') + linesep125        if isinstance(self, Reader):126            if hasattr(self, 'base'):127                r += 'BASE   : ' + repr(self.base) + (' [SUB]' if self.sub_tree else ' [LEVEL]') + linesep128            if hasattr(self, '_query') and self._query:129                r += 'QUERY  : ' + repr(self._query) + ('' if '(' in self._query else (' [AND]' if self.components_in_and else ' [OR]')) + linesep130            if hasattr(self, 'validated_query') and self.validated_query:131                r += 'PARSED : ' + repr(self.validated_query) + ('' if '(' in self._query else (' [AND]' if self.components_in_and else ' [OR]')) + linesep132            if hasattr(self, 'query_filter') and self.query_filter:133                r += 'FILTER : ' + repr(self.query_filter) + linesep134 135        if hasattr(self, 'execution_time') and self.execution_time:136            r += 'ENTRIES: ' + str(len(self.entries))137            r += ' [executed at: ' + str(self.execution_time.isoformat()) + ']' + linesep138 139        if self.failed:140            r += 'LAST OPERATION FAILED [' + str(len(self.errors)) + ' failure' + ('s' if len(self.errors) > 1 else '') + ' at operation' + ('s ' if len(self.errors) > 1 else ' ') + ', '.join([str(i) for i, error in enumerate(self.operations) if error.result['result'] != RESULT_SUCCESS]) + ']'141 142        return r143 144    def __str__(self):145        return self.__repr__()146 147    def __iter__(self):148        return self.entries.__iter__()149 150    def __getitem__(self, item):151        """Return indexed item, if index is not found then try to sequentially search in DN of entries.152        If only one entry is found return it else raise a KeyError exception. The exception message153        includes the number of entries that matches, if less than 10 entries match then show the DNs154        in the exception message.155        """156        try:157            return self.entries[item]158        except TypeError:159            pass160 161        if isinstance(item, STRING_TYPES):162            found = self.match_dn(item)163 164            if len(found) == 1:165                return found[0]166            elif len(found) > 1:167                error_message = 'Multiple entries found: %d entries match the text in dn' % len(found) + ('' if len(found) > 10 else (' [' + '; '.join([e.entry_dn for e in found]) + ']'))168                if log_enabled(ERROR):169                    log(ERROR, '%s for <%s>', error_message, self)170                raise KeyError(error_message)171 172        error_message = 'no entry found'173        if log_enabled(ERROR):174            log(ERROR, '%s for <%s>', error_message, self)175        raise KeyError(error_message)176 177    def __len__(self):178        return len(self.entries)179 180    if str is not bytes:  # Python 3181        def __bool__(self):  # needed to make the cursor appears as existing in "if cursor:" even if there are no entries182            return True183    else:  # Python 2184        def __nonzero__(self):185            return True186 187    def _get_attributes(self, response, attr_defs, entry):188        """Assign the result of the LDAP query to the Entry object dictionary.189 190        If the optional 'post_query' callable is present in the AttrDef it is called with each value of the attribute and the callable result is stored in the attribute.191 192        Returns the default value for missing attributes.193        If the 'dereference_dn' in AttrDef is a ObjectDef then the attribute values are treated as distinguished name and the relevant entry is retrieved and stored in the attribute value.194 195        """196        conf_operational_attribute_prefix = get_config_parameter('ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX')197        conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]198        attributes = CaseInsensitiveWithAliasDict()199        used_attribute_names = set()200        for attr in attr_defs:201            attr_def = attr_defs[attr]202            attribute_name = None203            for attr_name in response['attributes']:204                if attr_def.name.lower() == attr_name.lower():205                    attribute_name = attr_name206                    break207 208            if attribute_name or attr_def.default is not NotImplemented:  # attribute value found in result or default value present - NotImplemented allows use of None as default209                attribute = self.attribute_class(attr_def, entry, self)210                attribute.response = response211                attribute.raw_values = response['raw_attributes'][attribute_name] if attribute_name else None212                if attr_def.post_query and attr_def.name in response['attributes'] and response['raw_attributes'] != list():213                    attribute.values = attr_def.post_query(attr_def.key, response['attributes'][attribute_name])214                else:215                    if attr_def.default is NotImplemented or (attribute_name and response['raw_attributes'][attribute_name] != list()):216                        attribute.values = response['attributes'][attribute_name]217                    else:218                        attribute.values = attr_def.default if isinstance(attr_def.default, SEQUENCE_TYPES) else [attr_def.default]219                if not isinstance(attribute.values, list):  # force attribute values to list (if attribute is single-valued)220                    attribute.values = [attribute.values]221                if attr_def.dereference_dn:  # try to get object referenced in value222                    if attribute.values:223                        temp_reader = Reader(self.connection, attr_def.dereference_dn, base='', get_operational_attributes=self.get_operational_attributes, controls=self.controls)224                        temp_values = []225                        for element in attribute.values:226                            if entry.entry_dn != element:227                                temp_values.append(temp_reader.search_object(element))228                            else:229                                error_message = 'object %s is referencing itself in the \'%s\' attribute' % (entry.entry_dn, attribute.definition.name)230                                if log_enabled(ERROR):231                                    log(ERROR, '%s for <%s>', error_message, self)232                                raise LDAPObjectDereferenceError(error_message)233                        del temp_reader  # remove the temporary Reader234                        attribute.values = temp_values235                attributes[attribute.key] = attribute236                if attribute.other_names:237                    attributes.set_alias(attribute.key, attribute.other_names)238                if attr_def.other_names:239                    attributes.set_alias(attribute.key, attr_def.other_names)240                used_attribute_names.add(attribute_name)241 242        if self.attributes:243            used_attribute_names.update(self.attributes)244 245        for attribute_name in response['attributes']:246            if attribute_name not in used_attribute_names:247                operational_attribute = False248                # check if the type is an operational attribute249                if attribute_name in self.schema.attribute_types:250                    if self.schema.attribute_types[attribute_name].no_user_modification or self.schema.attribute_types[attribute_name].usage in [ATTRIBUTE_DIRECTORY_OPERATION, ATTRIBUTE_DISTRIBUTED_OPERATION, ATTRIBUTE_DSA_OPERATION]:251                        operational_attribute = True252                else:253                    operational_attribute = True254                if not operational_attribute and attribute_name not in attr_defs and attribute_name.lower() not in conf_attributes_excluded_from_object_def:255                    error_message = 'attribute \'%s\' not in object class \'%s\' for entry %s' % (attribute_name, ', '.join(entry.entry_definition._object_class), entry.entry_dn)256                    if log_enabled(ERROR):257                        log(ERROR, '%s for <%s>', error_message, self)258                    raise LDAPCursorError(error_message)259                attribute = OperationalAttribute(AttrDef(conf_operational_attribute_prefix + attribute_name), entry, self)260                attribute.raw_values = response['raw_attributes'][attribute_name]261                attribute.values = response['attributes'][attribute_name] if isinstance(response['attributes'][attribute_name], SEQUENCE_TYPES) else [response['attributes'][attribute_name]]262                if (conf_operational_attribute_prefix + attribute_name) not in attributes:263                    attributes[conf_operational_attribute_prefix + attribute_name] = attribute264 265        return attributes266 267    def match_dn(self, dn):268        """Return entries with text in DN"""269        matched = []270        for entry in self.entries:271            if dn.lower() in entry.entry_dn.lower():272                matched.append(entry)273        return matched274 275    def match(self, attributes, value):276        """Return entries with text in one of the specified attributes"""277        matched = []278        if not isinstance(attributes, SEQUENCE_TYPES):279            attributes = [attributes]280 281        for entry in self.entries:282            found = False283            for attribute in attributes:284                if attribute in entry:285                    for attr_value in entry[attribute].values:286                        if hasattr(attr_value, 'lower') and hasattr(value, 'lower') and value.lower() in attr_value.lower():287                            found = True288                        elif value == attr_value:289                            found = True290                        if found:291                            matched.append(entry)292                            break293                    if found:294                        break295                    # checks raw values, tries to convert value to byte296                    raw_value = to_raw(value)297                    if isinstance(raw_value, (bytes, bytearray)):298                        for attr_value in entry[attribute].raw_values:299                            if hasattr(attr_value, 'lower') and hasattr(raw_value, 'lower') and raw_value.lower() in attr_value.lower():300                                found = True301                            elif raw_value == attr_value:302                                found = True303                            if found:304                                matched.append(entry)305                                break306                        if found:307                            break308        return matched309 310    def _create_entry(self, response):311        if not response['type'] == 'searchResEntry':312            return None313 314        entry = self.entry_class(response['dn'], self)  # define an Entry (writable or readonly), as specified in the cursor definition315        entry._state.attributes = self._get_attributes(response, self.definition._attributes, entry)316        entry._state.raw_attributes = deepcopy(response['raw_attributes'])317 318        entry._state.response = response319        entry._state.read_time = datetime.now()320        entry._state.set_status(self.entry_initial_status)321        for attr in entry:  # returns the whole attribute object322            entry.__dict__[attr.key] = attr323 324        return entry325 326    def _execute_query(self, query_scope, attributes):327        if not self.connection:328            error_message = 'no connection established'329            if log_enabled(ERROR):330                log(ERROR, '%s for <%s>', error_message, self)331            raise LDAPCursorError(error_message)332        old_query_filter = None333        if query_scope == BASE:  # requesting a single object so an always-valid filter is set334            if hasattr(self, 'query_filter'):  # only Reader has a query filter335                old_query_filter = self.query_filter336            self.query_filter = '(objectclass=*)'337        else:338            self._create_query_filter()339        if log_enabled(PROTOCOL):340            log(PROTOCOL, 'executing query - base: %s - filter: %s - scope: %s for <%s>', self.base, self.query_filter, query_scope, self)341        with self.connection:342            result = self.connection.search(search_base=self.base,343                                            search_filter=self.query_filter,344                                            search_scope=query_scope,345                                            dereference_aliases=self.dereference_aliases,346                                            attributes=attributes if attributes else list(self.attributes),347                                            get_operational_attributes=self.get_operational_attributes,348                                            controls=self.controls)349            if not self.connection.strategy.sync:350                response, result, request = self.connection.get_response(result, get_request=True)351            else:352                if self.connection.strategy.thread_safe:353                    _, result, response, _ = result354                else:355                    response = self.connection.response356                    result = self.connection.result357                request = self.connection.request358 359        self._store_operation_in_history(request, result, response)360 361        if self._do_not_reset:  # trick to not remove entries when using _refresh()362            return self._create_entry(response[0])363 364        self.entries = []365        for r in response:366            entry = self._create_entry(r)367            if entry is not None:368                self.entries.append(entry)369                if 'objectClass' in entry:370                    for object_class in entry.objectClass:371                        if self.schema and self.schema.object_classes[object_class].kind == CLASS_AUXILIARY and object_class not in self.definition._auxiliary_class:372                            # add auxiliary class to object definition373                            self.definition._auxiliary_class.append(object_class)374                            self.definition._populate_attr_defs(object_class)375        self.execution_time = datetime.now()376 377        if old_query_filter:  # requesting a single object so an always-valid filter is set378            self.query_filter = old_query_filter379 380    def remove(self, entry):381        if log_enabled(PROTOCOL):382            log(PROTOCOL, 'removing entry <%s> in <%s>', entry, self)383        self.entries.remove(entry)384 385    def _reset_history(self):386        self._operation_history = list()387 388    def _store_operation_in_history(self, request, result, response):389        self._operation_history.append(Operation(request, result, response))390 391    @property392    def operations(self):393        return self._operation_history394 395    @property396    def errors(self):397        return [error for error in self._operation_history if error.result['result'] != RESULT_SUCCESS]398 399    @property400    def failed(self):401        if hasattr(self, '_operation_history'):402            return any([error.result['result'] != RESULT_SUCCESS for error in self._operation_history])403 404 405class Reader(Cursor):406    """Reader object to perform searches:407 408    :param connection: the LDAP connection object to use409    :type connection: LDAPConnection410    :param object_def: the ObjectDef of the LDAP object returned411    :type object_def: ObjectDef412    :param query: the simplified query (will be transformed in an LDAP filter)413    :type query: str414    :param base: starting base of the search415    :type base: str416    :param components_in_and: specify if assertions in the query must all be satisfied or not (AND/OR)417    :type components_in_and: bool418    :param sub_tree: specify if the search must be performed ad Single Level (False) or Whole SubTree (True)419    :type sub_tree: bool420    :param get_operational_attributes: specify if operational attributes are returned or not421    :type get_operational_attributes: bool422    :param controls: controls to be used in search423    :type controls: tuple424 425    """426    entry_class = Entry  # entries are read_only427    attribute_class = Attribute  # attributes are read_only428    entry_initial_status = STATUS_READ429 430    def __init__(self, connection, object_def, base, query='', components_in_and=True, sub_tree=True, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):431        Cursor.__init__(self, connection, object_def, get_operational_attributes, attributes, controls, auxiliary_class)432        self._components_in_and = components_in_and433        self.sub_tree = sub_tree434        self._query = query435        self.base = base436        self.dereference_aliases = DEREF_ALWAYS437        self.validated_query = None438        self._query_dict = dict()439        self._validated_query_dict = dict()440        self.query_filter = None441        self.reset()442 443        if log_enabled(BASIC):444            log(BASIC, 'instantiated Reader Cursor: <%r>', self)445 446    @property447    def query(self):448        return self._query449 450    @query.setter451    def query(self, value):452        self._query = value453        self.reset()454 455    @property456    def components_in_and(self):457        return self._components_in_and458 459    @components_in_and.setter460    def components_in_and(self, value):461        self._components_in_and = value462        self.reset()463 464    def clear(self):465        """Clear the Reader search parameters466 467        """468        self.dereference_aliases = DEREF_ALWAYS469        self._reset_history()470 471    def reset(self):472        """Clear all the Reader parameters473 474        """475        self.clear()476        self.validated_query = None477        self._query_dict = dict()478        self._validated_query_dict = dict()479        self.execution_time = None480        self.query_filter = None481        self.entries = []482        self._create_query_filter()483 484    def _validate_query(self):485        """Processes the text query and verifies that the requested friendly names are in the Reader dictionary486        If the AttrDef has a 'validate' property the callable is executed and if it returns False an Exception is raised487 488        """489        if not self._query_dict:490            self._query_dict = _create_query_dict(self._query)491 492        query = ''493        for d in sorted(self._query_dict):494            attr = d[1:] if d[0] in '&|' else d495            for attr_def in self.definition:496                if ''.join(attr.split()).lower() == attr_def.key.lower():497                    attr = attr_def.key498                    break499            if attr in self.definition:500                vals = sorted(self._query_dict[d].split(';'))501 502                query += (d[0] + attr if d[0] in '&|' else attr) + ': '503                for val in vals:504                    val = val.strip()505                    val_not = True if val[0] == '!' else False506                    val_search_operator = '='  # default507                    if val_not:508                        if val[1:].lstrip()[0] not in '=<>~':509                            value = val[1:].lstrip()510                        else:511                            val_search_operator = val[1:].lstrip()[0]512                            value = val[1:].lstrip()[1:]513                    else:514                        if val[0] not in '=<>~':515                            value = val.lstrip()516                        else:517                            val_search_operator = val[0]518                            value = val[1:].lstrip()519 520                    if self.definition[attr].validate:521                        validated = self.definition[attr].validate(value)  # returns True, False or a value to substitute to the actual values522                        if validated is False:523                            error_message = 'validation failed for attribute %s and value %s' % (d, val)524                            if log_enabled(ERROR):525                                log(ERROR, '%s for <%s>', error_message, self)526                            raise LDAPCursorError(error_message)527                        elif validated is not True:  # a valid LDAP value equivalent to the actual values528                                value = validated529                    if val_not:530                        query += '!' + val_search_operator + str(value)531                    else:532                        query += val_search_operator + str(value)533 534                    query += ';'535                query = query[:-1] + ', '536            else:537                error_message = 'attribute \'%s\' not in definition' % attr538                if log_enabled(ERROR):539                    log(ERROR, '%s for <%s>', error_message, self)540                raise LDAPCursorError(error_message)541        self.validated_query = query[:-2]542        self._validated_query_dict = _create_query_dict(self.validated_query)543 544    def _create_query_filter(self):545        """Converts the query dictionary to the filter text"""546        self.query_filter = ''547 548        if self.definition._object_class:549            self.query_filter += '(&'550            if isinstance(self.definition._object_class, SEQUENCE_TYPES) and len(self.definition._object_class) == 1:551                self.query_filter += '(objectClass=' + self.definition._object_class[0] + ')'552            elif isinstance(self.definition._object_class, SEQUENCE_TYPES):553                self.query_filter += '(&'554                for object_class in self.definition._object_class:555                    self.query_filter += '(objectClass=' + object_class + ')'556                self.query_filter += ')'557            else:558                error_message = 'object class must be a string or a list'559                if log_enabled(ERROR):560                    log(ERROR, '%s for <%s>', error_message, self)561                raise LDAPCursorError(error_message)562 563        if self._query and self._query.startswith('(') and self._query.endswith(')'):  # query is already an LDAP filter564            if 'objectclass' not in self._query.lower():565                self.query_filter += self._query + ')'  # if objectclass not in filter adds from definition566            else:567                self.query_filter = self._query568            return569        elif self._query:  # if a simplified filter is present570            if not self.components_in_and:571                self.query_filter += '(|'572            elif not self.definition._object_class:573                self.query_filter += '(&'574 575            self._validate_query()576 577            attr_counter = 0578            for attr in sorted(self._validated_query_dict):579                attr_counter += 1580                multi = True if ';' in self._validated_query_dict[attr] else False581                vals = sorted(self._validated_query_dict[attr].split(';'))582                attr_def = self.definition[attr[1:]] if attr[0] in '&|' else self.definition[attr]583                if attr_def.pre_query:584                    modvals = []585                    for val in vals:586                        modvals.append(val[0] + attr_def.pre_query(attr_def.key, val[1:]))587                    vals = modvals588                if multi:589                    if attr[0] in '&|':590                        self.query_filter += '(' + attr[0]591                    else:592                        self.query_filter += '(|'593 594                for val in vals:595                    if val[0] == '!':596                        self.query_filter += '(!(' + attr_def.name + _ret_search_value(val[1:]) + '))'597                    else:598                        self.query_filter += '(' + attr_def.name + _ret_search_value(val) + ')'599                if multi:600                    self.query_filter += ')'601 602            if not self.components_in_and:603                self.query_filter += '))'604            else:605                self.query_filter += ')'606 607            if not self.definition._object_class and attr_counter == 1:  # removes unneeded starting filter608                self.query_filter = self.query_filter[2: -1]609 610            if self.query_filter == '(|)' or self.query_filter == '(&)':  # removes empty filter611                self.query_filter = ''612        else:  # no query, remove unneeded leading (&613            self.query_filter = self.query_filter[2:]614 615    def search(self, attributes=None):616        """Perform the LDAP search617 618        :return: Entries found in search619 620        """621        self.clear()622        query_scope = SUBTREE if self.sub_tree else LEVEL623        if log_enabled(PROTOCOL):624            log(PROTOCOL, 'performing search in <%s>', self)625        self._execute_query(query_scope, attributes)626 627        return self.entries628 629    def search_object(self, entry_dn=None, attributes=None):  # base must be a single dn630        """Perform the LDAP search operation SINGLE_OBJECT scope631 632        :return: Entry found in search633 634        """635        if log_enabled(PROTOCOL):636            log(PROTOCOL, 'performing object search in <%s>', self)637        self.clear()638        if entry_dn:639            old_base = self.base640            self.base = entry_dn641            self._execute_query(BASE, attributes)642            self.base = old_base643        else:644            self._execute_query(BASE, attributes)645 646        return self.entries[0] if len(self.entries) > 0 else None647 648    def search_level(self, attributes=None):649        """Perform the LDAP search operation with SINGLE_LEVEL scope650 651        :return: Entries found in search652 653        """654        if log_enabled(PROTOCOL):655            log(PROTOCOL, 'performing single level search in <%s>', self)656        self.clear()657        self._execute_query(LEVEL, attributes)658 659        return self.entries660 661    def search_subtree(self, attributes=None):662        """Perform the LDAP search operation WHOLE_SUBTREE scope663 664        :return: Entries found in search665 666        """667        if log_enabled(PROTOCOL):668            log(PROTOCOL, 'performing whole subtree search in <%s>', self)669        self.clear()670        self._execute_query(SUBTREE, attributes)671 672        return self.entries673 674    def _entries_generator(self, responses):675        for response in responses:676            yield self._create_entry(response)677 678    def search_paged(self, paged_size, paged_criticality=True, generator=True, attributes=None):679        """Perform a paged search, can be called as an Iterator680 681        :param attributes: optional attributes to search682        :param paged_size: number of entries returned in each search683        :type paged_size: int684        :param paged_criticality: specify if server must not execute the search if it is not capable of paging searches685        :type paged_criticality: bool686        :param generator: if True the paged searches are executed while generating the entries,687                          if False all the paged searches are execute before returning the generator688        :type generator: bool689        :return: Entries found in search690 691        """692        if log_enabled(PROTOCOL):693            log(PROTOCOL, 'performing paged search in <%s> with paged size %s', self, str(paged_size))694        if not self.connection:695            error_message = 'no connection established'696            if log_enabled(ERROR):697                log(ERROR, '%s for <%s>', error_message, self)698            raise LDAPCursorError(error_message)699 700        self.clear()701        self._create_query_filter()702        self.entries = []703        self.execution_time = datetime.now()704        response = self.connection.extend.standard.paged_search(search_base=self.base,705                                                                search_filter=self.query_filter,706                                                                search_scope=SUBTREE if self.sub_tree else LEVEL,707                                                                dereference_aliases=self.dereference_aliases,708                                                                attributes=attributes if attributes else self.attributes,709                                                                get_operational_attributes=self.get_operational_attributes,710                                                                controls=self.controls,711                                                                paged_size=paged_size,712                                                                paged_criticality=paged_criticality,713                                                                generator=generator)714        if generator:715            return self._entries_generator(response)716        else:717            return list(self._entries_generator(response))718 719 720class Writer(Cursor):721    entry_class = WritableEntry722    attribute_class = WritableAttribute723    entry_initial_status = STATUS_WRITABLE724 725    @staticmethod726    def from_cursor(cursor, connection=None, object_def=None, custom_validator=None):727        if connection is None:728            connection = cursor.connection729        if object_def is None:730            object_def = cursor.definition731        writer = Writer(connection, object_def, attributes=cursor.attributes)732        for entry in cursor.entries:733            if isinstance(cursor, Reader):734                entry.entry_writable(object_def, writer, custom_validator=custom_validator)735            elif isinstance(cursor, Writer):736                pass737            else:738                error_message = 'unknown cursor type %s' % str(type(cursor))739                if log_enabled(ERROR):740                    log(ERROR, '%s', error_message)741                raise LDAPCursorError(error_message)742        writer.execution_time = cursor.execution_time743        if log_enabled(BASIC):744            log(BASIC, 'instantiated Writer Cursor <%r> from cursor <%r>', writer, cursor)745        return writer746 747    @staticmethod748    def from_response(connection, object_def, response=None):749        if response is None:750            if not connection.strategy.sync:751                error_message = 'with asynchronous strategies response must be specified'752                if log_enabled(ERROR):753                    log(ERROR, '%s', error_message)754                raise LDAPCursorError(error_message)755            elif connection.response:756                response = connection.response757            else:758                error_message = 'response not present'759                if log_enabled(ERROR):760                    log(ERROR, '%s', error_message)761                raise LDAPCursorError(error_message)762        writer = Writer(connection, object_def)763 764        for resp in response:765            if resp['type'] == 'searchResEntry':766                entry = writer._create_entry(resp)767                writer.entries.append(entry)768        if log_enabled(BASIC):769            log(BASIC, 'instantiated Writer Cursor <%r> from response', writer)770        return writer771 772    def __init__(self, connection, object_def, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):773        Cursor.__init__(self, connection, object_def, get_operational_attributes, attributes, controls, auxiliary_class)774        self.dereference_aliases = DEREF_NEVER775 776        if log_enabled(BASIC):777            log(BASIC, 'instantiated Writer Cursor: <%r>', self)778 779    def commit(self, refresh=True):780        if log_enabled(PROTOCOL):781            log(PROTOCOL, 'committed changes for <%s>', self)782        self._reset_history()783        successful = True784        for entry in self.entries:785            if not entry.entry_commit_changes(refresh=refresh, controls=self.controls, clear_history=False):786                successful = False787 788        self.execution_time = datetime.now()789 790        return successful791 792    def discard(self):793        if log_enabled(PROTOCOL):794            log(PROTOCOL, 'discarded changes for <%s>', self)795        for entry in self.entries:796            entry.entry_discard_changes()797 798    def _refresh_object(self, entry_dn, attributes=None, tries=4, seconds=2, controls=None):  # base must be a single dn799        """Performs the LDAP search operation SINGLE_OBJECT scope800 801        :return: Entry found in search802 803        """804        if log_enabled(PROTOCOL):805            log(PROTOCOL, 'refreshing object <%s> for <%s>', entry_dn, self)806        if not self.connection:807            error_message = 'no connection established'808            if log_enabled(ERROR):809                log(ERROR, '%s for <%s>', error_message, self)810            raise LDAPCursorError(error_message)811 812        response = []813        with self.connection:814            counter = 0815            while counter < tries:816                result = self.connection.search(search_base=entry_dn,817                                                search_filter='(objectclass=*)',818                                                search_scope=BASE,819                                                dereference_aliases=DEREF_NEVER,820                                                attributes=attributes if attributes else self.attributes,821                                                get_operational_attributes=self.get_operational_attributes,822                                                controls=controls)823                if not self.connection.strategy.sync:824                    response, result, request = self.connection.get_response(result, get_request=True)825                else:826                    if self.connection.strategy.thread_safe:827                        _, result, response, request = result828                    else:829                        response = self.connection.response830                        result = self.connection.result831                        request = self.connection.request832 833                if result['result'] in [RESULT_SUCCESS]:834                    break835                sleep(seconds)836                counter += 1837                self._store_operation_in_history(request, result, response)838 839        if len(response) == 1:840            return self._create_entry(response[0])841        elif len(response) == 0:842            return None843 844        error_message = 'more than 1 entry returned for a single object search'845        if log_enabled(ERROR):846            log(ERROR, '%s for <%s>', error_message, self)847        raise LDAPCursorError(error_message)848 849    def new(self, dn):850        if log_enabled(BASIC):851            log(BASIC, 'creating new entry <%s> for <%s>', dn, self)852        dn = safe_dn(dn)853        for entry in self.entries:  # checks if dn is already used in an cursor entry854            if entry.entry_dn == dn:855                error_message = 'dn already present in cursor'856                if log_enabled(ERROR):857                    log(ERROR, '%s for <%s>', error_message, self)858                raise LDAPCursorError(error_message)859        rdns = safe_rdn(dn, decompose=True)860        entry = self.entry_class(dn, self)  # defines a new empty Entry861        for attr in entry.entry_mandatory_attributes:  # defines all mandatory attributes as virtual862                entry._state.attributes[attr] = self.attribute_class(entry._state.definition[attr], entry, self)863                entry.__dict__[attr] = entry._state.attributes[attr]864        entry.objectclass.set(self.definition._object_class)865        for rdn in rdns:  # adds virtual attributes from rdns in entry name (should be more than one with + syntax)866            if rdn[0] in entry._state.definition._attributes:867                rdn_name = entry._state.definition._attributes[rdn[0]].name  # normalize case folding868                if rdn_name not in entry._state.attributes:869                    entry._state.attributes[rdn_name] = self.attribute_class(entry._state.definition[rdn_name], entry, self)870                    entry.__dict__[rdn_name] = entry._state.attributes[rdn_name]871                entry.__dict__[rdn_name].set(rdn[1])872            else:873                error_message = 'rdn type \'%s\' not in object class definition' % rdn[0]874                if log_enabled(ERROR):875                    log(ERROR, '%s for <%s>', error_message, self)876                raise LDAPCursorError(error_message)877        entry._state.set_status(STATUS_VIRTUAL)  # set intial status878        entry._state.set_status(STATUS_PENDING_CHANGES)  # tries to change status to PENDING_CHANGES. If mandatory attributes are missing status is reverted to MANDATORY_MISSING879        self.entries.append(entry)880        return entry881 882    def refresh_entry(self, entry, tries=4, seconds=2):883        conf_operational_attribute_prefix = get_config_parameter('ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX')884 885        self._do_not_reset = True886        attr_list = []887        if log_enabled(PROTOCOL):888            log(PROTOCOL, 'refreshing entry <%s> for <%s>', entry, self)889        for attr in entry._state.attributes:  # check friendly attribute name in AttrDef, do not check operational attributes890            if attr.lower().startswith(conf_operational_attribute_prefix.lower()):891                continue892            if entry._state.definition[attr].name:893                attr_list.append(entry._state.definition[attr].name)894            else:895                attr_list.append(entry._state.definition[attr].key)896 897        temp_entry = self._refresh_object(entry.entry_dn, attr_list, tries, seconds=seconds)  # if any attributes is added adds only to the entry not to the definition898        self._do_not_reset = False899        if temp_entry:900            temp_entry._state.origin = entry._state.origin901            entry.__dict__.clear()902            entry.__dict__['_state'] = temp_entry._state903            for attr in entry._state.attributes:  # returns the attribute key904                entry.__dict__[attr] = entry._state.attributes[attr]905 906            for attr in entry.entry_attributes:  # if any attribute of the class was deleted makes it virtual907                if attr not in entry._state.attributes and attr in entry.entry_definition._attributes:908                    entry._state.attributes[attr] = WritableAttribute(entry.entry_definition[attr], entry, self)909                    entry.__dict__[attr] = entry._state.attributes[attr]910            entry._state.set_status(entry._state._initial_status)911            return True912        return False913 
codekingpro/portable-devtools · Team Ai