codekingpro/portable-devtools
114k
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/>.25 26from os import linesep27 28from .. import MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE, SEQUENCE_TYPES29from ..core.exceptions import LDAPCursorError30from ..utils.repr import to_stdout_encoding31from . import STATUS_PENDING_CHANGES, STATUS_VIRTUAL, STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING32from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED33 34 35# noinspection PyUnresolvedReferences36class Attribute(object):37 """Attribute/values object, it includes the search result (after post_query transformation) of each attribute in an entry38 39 Attribute object is read only40 41 - values: contain the processed attribute values42 - raw_values': contain the unprocessed attribute values43 44 45 """46 47 def __init__(self, attr_def, entry, cursor):48 self.key = attr_def.key49 self.definition = attr_def50 self.values = []51 self.raw_values = []52 self.response = None53 self.entry = entry54 self.cursor = cursor55 other_names = [name for name in attr_def.oid_info.name if self.key.lower() != name.lower()] if attr_def.oid_info else None56 self.other_names = set(other_names) if other_names else None # self.other_names is None if there are no short names, else is a set of secondary names57 58 def __repr__(self):59 if len(self.values) == 1:60 r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])61 elif len(self.values) > 1:62 r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])63 filler = ' ' * (len(self.key) + 6)64 for value in self.values[1:]:65 r += linesep + filler + to_stdout_encoding(value)66 else:67 r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding('<no value>')68 69 return r70 71 def __str__(self):72 if len(self.values) == 1:73 return to_stdout_encoding(self.values[0])74 else:75 return to_stdout_encoding(self.values)76 77 def __len__(self):78 return len(self.values)79 80 def __iter__(self):81 return self.values.__iter__()82 83 def __getitem__(self, item):84 return self.values[item]85 86 def __getstate__(self):87 cpy = dict(self.__dict__)88 cpy['cursor'] = None89 return cpy90 91 def __eq__(self, other):92 try:93 if self.value == other:94 return True95 except Exception:96 return False97 98 def __ne__(self, other):99 return not self == other100 101 @property102 def value(self):103 """104 :return: The single value or a list of values of the attribute.105 """106 if not self.values:107 return None108 109 return self.values[0] if len(self.values) == 1 else self.values110 111 112class OperationalAttribute(Attribute):113 """Operational attribute/values object. Include the search result of an114 operational attribute in an entry115 116 OperationalAttribute object is read only117 118 - values: contains the processed attribute values119 - raw_values: contains the unprocessed attribute values120 121 It may not have an AttrDef122 123 """124 125 def __repr__(self):126 if len(self.values) == 1:127 r = to_stdout_encoding(self.key) + ' [OPERATIONAL]: ' + to_stdout_encoding(self.values[0])128 elif len(self.values) > 1:129 r = to_stdout_encoding(self.key) + ' [OPERATIONAL]: ' + to_stdout_encoding(self.values[0])130 filler = ' ' * (len(self.key) + 6)131 for value in sorted(self.values[1:]):132 r += linesep + filler + to_stdout_encoding(value)133 else:134 r = ''135 136 return r137 138 139class WritableAttribute(Attribute):140 def __repr__(self):141 filler = ' ' * (len(self.key) + 6)142 if len(self.values) == 1:143 r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])144 elif len(self.values) > 1:145 r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])146 for value in self.values[1:]:147 r += linesep + filler + to_stdout_encoding(value)148 else:149 r = to_stdout_encoding(self.key) + to_stdout_encoding(': <Virtual>')150 if self.definition.name in self.entry._changes:151 r += linesep + filler + 'CHANGES: ' + str(self.entry._changes[self.definition.name])152 return r153 154 def __iadd__(self, other):155 self.add(other)156 return Ellipsis # hack to avoid calling set() in entry __setattr__157 158 def __isub__(self, other):159 self.delete(other)160 return Ellipsis # hack to avoid calling set_value in entry __setattr__161 162 def _update_changes(self, changes, remove_old=False):163 # checks for friendly key in AttrDef and uses the real attribute name164 if self.definition and self.definition.name:165 key = self.definition.name166 else:167 key = self.key168 169 if key not in self.entry._changes or remove_old: # remove old changes (for removing attribute)170 self.entry._changes[key] = []171 172 self.entry._changes[key].append(changes)173 if log_enabled(PROTOCOL):174 log(PROTOCOL, 'updated changes <%r> for <%s> attribute in <%s> entry', changes, self.key, self.entry.entry_dn)175 self.entry._state.set_status(STATUS_PENDING_CHANGES)176 177 def add(self, values):178 if log_enabled(PROTOCOL):179 log(PROTOCOL, 'adding %r to <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)180 # new value for attribute to commit with a MODIFY_ADD181 if self.entry._state._initial_status == STATUS_VIRTUAL:182 error_message = 'cannot perform a modify operation in a new entry'183 if log_enabled(ERROR):184 log(ERROR, '%s for <%s>', error_message, self)185 raise LDAPCursorError(error_message)186 if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:187 error_message = self.entry.entry_status + ' - cannot add attributes'188 if log_enabled(ERROR):189 log(ERROR, '%s for <%s>', error_message, self)190 raise LDAPCursorError(error_message)191 if values is None:192 error_message = 'value to add cannot be None'193 if log_enabled(ERROR):194 log(ERROR, '%s for <%s>', error_message, self)195 raise LDAPCursorError(error_message)196 if values is not None:197 validated = self.definition.validate(values) # returns True, False or a value to substitute to the actual values198 if validated is False:199 error_message = 'value \'%s\' non valid for attribute \'%s\'' % (values, self.key)200 if log_enabled(ERROR):201 log(ERROR, '%s for <%s>', error_message, self)202 raise LDAPCursorError(error_message)203 elif validated is not True: # a valid LDAP value equivalent to the actual values204 values = validated205 self._update_changes((MODIFY_ADD, values if isinstance(values, SEQUENCE_TYPES) else [values]))206 207 def set(self, values):208 # new value for attribute to commit with a MODIFY_REPLACE, old values are deleted209 if log_enabled(PROTOCOL):210 log(PROTOCOL, 'setting %r to <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)211 if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:212 error_message = self.entry.entry_status + ' - cannot set attributes'213 if log_enabled(ERROR):214 log(ERROR, '%s for <%s>', error_message, self)215 raise LDAPCursorError(error_message)216 if values is None:217 error_message = 'new value cannot be None'218 if log_enabled(ERROR):219 log(ERROR, '%s for <%s>', error_message, self)220 raise LDAPCursorError(error_message)221 validated = self.definition.validate(values) # returns True, False or a value to substitute to the actual values222 if validated is False:223 error_message = 'value \'%s\' non valid for attribute \'%s\'' % (values, self.key)224 if log_enabled(ERROR):225 log(ERROR, '%s for <%s>', error_message, self)226 raise LDAPCursorError(error_message)227 elif validated is not True: # a valid LDAP value equivalent to the actual values228 values = validated229 self._update_changes((MODIFY_REPLACE, values if isinstance(values, SEQUENCE_TYPES) else [values]), remove_old=True)230 231 def delete(self, values):232 # value for attribute to delete in commit with a MODIFY_DELETE233 if log_enabled(PROTOCOL):234 log(PROTOCOL, 'deleting %r from <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)235 if self.entry._state._initial_status == STATUS_VIRTUAL:236 error_message = 'cannot delete an attribute value in a new entry'237 if log_enabled(ERROR):238 log(ERROR, '%s for <%s>', error_message, self)239 raise LDAPCursorError(error_message)240 if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:241 error_message = self.entry.entry_status + ' - cannot delete attributes'242 if log_enabled(ERROR):243 log(ERROR, '%s for <%s>', error_message, self)244 raise LDAPCursorError(error_message)245 if values is None:246 error_message = 'value to delete cannot be None'247 if log_enabled(ERROR):248 log(ERROR, '%s for <%s>', error_message, self)249 raise LDAPCursorError(error_message)250 if not isinstance(values, SEQUENCE_TYPES):251 values = [values]252 for single_value in values:253 if single_value not in self.values:254 error_message = 'value \'%s\' not present in \'%s\'' % (single_value, ', '.join(self.values))255 if log_enabled(ERROR):256 log(ERROR, '%s for <%s>', error_message, self)257 raise LDAPCursorError(error_message)258 self._update_changes((MODIFY_DELETE, values))259 260 def remove(self):261 if log_enabled(PROTOCOL):262 log(PROTOCOL, 'removing <%s> attribute in <%s> entry', self.key, self.entry.entry_dn)263 if self.entry._state._initial_status == STATUS_VIRTUAL:264 error_message = 'cannot remove an attribute in a new entry'265 if log_enabled(ERROR):266 log(ERROR, '%s for <%s>', error_message, self)267 raise LDAPCursorError(error_message)268 if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:269 error_message = self.entry.entry_status + ' - cannot remove attributes'270 if log_enabled(ERROR):271 log(ERROR, '%s for <%s>', error_message, self)272 raise LDAPCursorError(error_message)273 self._update_changes((MODIFY_REPLACE, []), True)274 275 def discard(self):276 if log_enabled(PROTOCOL):277 log(PROTOCOL, 'discarding <%s> attribute in <%s> entry', self.key, self.entry.entry_dn)278 del self.entry._changes[self.key]279 if not self.entry._changes:280 self.entry._state.set_status(self.entry._state._initial_status)281 282 @property283 def virtual(self):284 return False if len(self.values) else True285 286 @property287 def changes(self):288 if self.key in self.entry._changes:289 return self.entry._changes[self.key]290 return None291 