codekingpro/portable-devtools
114k
1"""2"""3 4# Created on 2016.08.095#6# Author: Giovanni Cannata7#8# Copyright 2016 - 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 binascii import a2b_hex, hexlify26from datetime import datetime27from calendar import timegm28from uuid import UUID29from struct import pack30 31 32from ... import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, INTEGER_TYPES33from .formatters import format_time, format_ad_timestamp34from ...utils.conv import to_raw, to_unicode, ldap_escape_to_bytes, escape_bytes35 36# Validators return True if value is valid, False if value is not valid,37# or a value different from True and False that is a valid value to substitute to the input value38 39 40def check_backslash(value):41 if isinstance(value, (bytearray, bytes)):42 if b'\\' in value:43 value = value.replace(b'\\', b'\\5C')44 elif isinstance(value, STRING_TYPES):45 if '\\' in value:46 value = value.replace('\\', '\\5C')47 return value48 49 50def check_type(input_value, value_type):51 if isinstance(input_value, value_type):52 return True53 54 if isinstance(input_value, SEQUENCE_TYPES):55 for value in input_value:56 if not isinstance(value, value_type):57 return False58 return True59 60 return False61 62 63# noinspection PyUnusedLocal64def always_valid(input_value):65 return True66 67 68def validate_generic_single_value(input_value):69 if not isinstance(input_value, SEQUENCE_TYPES):70 return True71 72 try: # object couldn't have a __len__ method73 if len(input_value) == 1:74 return True75 except Exception:76 pass77 78 return False79 80 81def validate_zero_and_minus_one_and_positive_int(input_value):82 """Accept -1 and 0 only (used by pwdLastSet in AD)83 """84 if not isinstance(input_value, SEQUENCE_TYPES):85 if isinstance(input_value, NUMERIC_TYPES) or isinstance(input_value, STRING_TYPES):86 return True if int(input_value) >= -1 else False87 return False88 else:89 if len(input_value) == 1 and (isinstance(input_value[0], NUMERIC_TYPES) or isinstance(input_value[0], STRING_TYPES)):90 return True if int(input_value[0]) >= -1 else False91 92 return False93 94 95def validate_integer(input_value):96 if check_type(input_value, (float, bool)):97 return False98 if check_type(input_value, INTEGER_TYPES):99 return True100 101 if not isinstance(input_value, SEQUENCE_TYPES):102 sequence = False103 input_value = [input_value]104 else:105 sequence = True # indicates if a sequence must be returned106 107 valid_values = [] # builds a list of valid int values108 from decimal import Decimal, InvalidOperation109 for element in input_value:110 try: #try to convert any type to int, an invalid conversion raise TypeError or ValueError, doublecheck with Decimal type, if both are valid and equal then then int() value is used111 value = to_unicode(element) if isinstance(element, bytes) else element112 decimal_value = Decimal(value)113 int_value = int(value)114 if decimal_value == int_value:115 valid_values.append(int_value)116 else:117 return False118 except (ValueError, TypeError, InvalidOperation):119 return False120 121 if sequence:122 return valid_values123 else:124 return valid_values[0]125 126 127def validate_bytes(input_value):128 return check_type(input_value, bytes)129 130 131def validate_boolean(input_value):132 # it could be a real bool or the string TRUE or FALSE, # only a single valued is allowed133 if validate_generic_single_value(input_value): # valid only if a single value or a sequence with a single element134 if isinstance(input_value, SEQUENCE_TYPES):135 input_value = input_value[0]136 if isinstance(input_value, bool):137 if input_value:138 return 'TRUE'139 else:140 return 'FALSE'141 if str is not bytes and isinstance(input_value, bytes): # python3 try to converts bytes to string142 input_value = to_unicode(input_value)143 if isinstance(input_value, STRING_TYPES):144 if input_value.lower() == 'true':145 return 'TRUE'146 elif input_value.lower() == 'false':147 return 'FALSE'148 return False149 150 151def validate_time_with_0_year(input_value):152 # validates generalized time but accept a 0000 year too153 # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC154 if not isinstance(input_value, SEQUENCE_TYPES):155 sequence = False156 input_value = [input_value]157 else:158 sequence = True # indicates if a sequence must be returned159 160 valid_values = []161 changed = False162 for element in input_value:163 if str is not bytes and isinstance(element, bytes): # python3 try to converts bytes to string164 element = to_unicode(element)165 if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time166 if element.startswith('0000') or isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string167 valid_values.append(element)168 else:169 return False170 elif isinstance(element, datetime):171 changed = True172 if element.tzinfo: # a datetime with a timezone173 valid_values.append(element.strftime('%Y%m%d%H%M%S%z'))174 else: # datetime without timezone, assumed local and adjusted to UTC175 offset = datetime.now() - datetime.utcnow()176 valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ'))177 else:178 return False179 180 if changed:181 if sequence:182 return valid_values183 else:184 return valid_values[0]185 else:186 return True187 188 189def validate_time(input_value):190 # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC191 if not isinstance(input_value, SEQUENCE_TYPES):192 sequence = False193 input_value = [input_value]194 else:195 sequence = True # indicates if a sequence must be returned196 197 valid_values = []198 changed = False199 for element in input_value:200 if str is not bytes and isinstance(element, bytes): # python3 try to converts bytes to string201 element = to_unicode(element)202 if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time203 if isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string204 valid_values.append(element)205 else:206 return False207 elif isinstance(element, datetime):208 changed = True209 if element.tzinfo: # a datetime with a timezone210 valid_values.append(element.strftime('%Y%m%d%H%M%S%z'))211 else: # datetime without timezone, assumed local and adjusted to UTC212 offset = datetime.now() - datetime.utcnow()213 valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ'))214 else:215 return False216 217 if changed:218 if sequence:219 return valid_values220 else:221 return valid_values[0]222 else:223 return True224 225 226def validate_ad_timestamp(input_value):227 """228 Active Directory stores date/time values as the number of 100-nanosecond intervals229 that have elapsed since the 0 hour on January 1, 1601 till the date/time that is being stored.230 The time is always stored in Greenwich Mean Time (GMT) in the Active Directory.231 """232 if not isinstance(input_value, SEQUENCE_TYPES):233 sequence = False234 input_value = [input_value]235 else:236 sequence = True # indicates if a sequence must be returned237 238 valid_values = []239 changed = False240 for element in input_value:241 if str is not bytes and isinstance(element, bytes): # python3 try to converts bytes to string242 element = to_unicode(element)243 if isinstance(element, NUMERIC_TYPES):244 if 0 <= element <= 9223372036854775807: # min and max for the AD timestamp starting from 12:00 AM January 1, 1601245 valid_values.append(element)246 else:247 return False248 elif isinstance(element, STRING_TYPES): # tries to check if it is already be a AD timestamp249 if isinstance(format_ad_timestamp(to_raw(element)), datetime): # valid Generalized Time string250 valid_values.append(element)251 else:252 return False253 elif isinstance(element, datetime):254 changed = True255 if element.tzinfo: # a datetime with a timezone256 valid_values.append(to_raw((timegm(element.utctimetuple()) + 11644473600) * 10000000, encoding='ascii'))257 else: # datetime without timezone, assumed local and adjusted to UTC258 offset = datetime.now() - datetime.utcnow()259 valid_values.append(to_raw((timegm((element - offset).timetuple()) + 11644473600) * 10000000, encoding='ascii'))260 else:261 return False262 263 if changed:264 if sequence:265 return valid_values266 else:267 return valid_values[0]268 else:269 return True270 271 272def validate_ad_timedelta(input_value):273 """274 Should be validated like an AD timestamp except that since it is a time275 delta, it is stored as a negative number.276 """277 if not isinstance(input_value, INTEGER_TYPES) or input_value > 0:278 return False279 return validate_ad_timestamp(input_value * -1)280 281 282def validate_guid(input_value):283 """284 object guid in uuid format (Novell eDirectory)285 """286 if not isinstance(input_value, SEQUENCE_TYPES):287 sequence = False288 input_value = [input_value]289 else:290 sequence = True # indicates if a sequence must be returned291 292 valid_values = []293 changed = False294 for element in input_value:295 if isinstance(element, STRING_TYPES):296 try:297 valid_values.append(UUID(element).bytes)298 changed = True299 except ValueError: # try if the value is an escaped ldap byte sequence300 try:301 x = ldap_escape_to_bytes(element)302 valid_values.append(UUID(bytes=x).bytes)303 changed = True304 continue305 except ValueError:306 if str is not bytes: # python 3307 pass308 else:309 valid_values.append(element)310 continue311 return False312 elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid313 valid_values.append(element)314 else:315 return False316 317 if changed:318 # valid_values = [check_backslash(value) for value in valid_values]319 if sequence:320 return valid_values321 else:322 return valid_values[0]323 else:324 return True325 326 327def validate_uuid(input_value):328 """329 object entryUUID in uuid format330 """331 if not isinstance(input_value, SEQUENCE_TYPES):332 sequence = False333 input_value = [input_value]334 else:335 sequence = True # indicates if a sequence must be returned336 337 valid_values = []338 changed = False339 for element in input_value:340 if isinstance(element, STRING_TYPES):341 try:342 valid_values.append(str(UUID(element)))343 changed = True344 except ValueError: # try if the value is an escaped byte sequence345 try:346 valid_values.append(str(UUID(element.replace('\\', ''))))347 changed = True348 continue349 except ValueError:350 if str is not bytes: # python 3351 pass352 else:353 valid_values.append(element)354 continue355 return False356 elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid357 valid_values.append(element)358 else:359 return False360 361 if changed:362 # valid_values = [check_backslash(value) for value in valid_values]363 if sequence:364 return valid_values365 else:366 return valid_values[0]367 else:368 return True369 370 371def validate_uuid_le(input_value):372 r"""373 Active Directory stores objectGUID in uuid_le format, follows RFC4122 and MS-DTYP:374 "{07039e68-4373-264d-a0a7-07039e684373}": string representation big endian, converted to little endian (with or without brace curles)375 "689e030773434d26a7a007039e684373": packet representation, already in little endian376 "\68\9e\03\07\73\43\4d\26\a7\a0\07\03\9e\68\43\73": bytes representation, already in little endian377 byte sequence: already in little endian378 379 """380 if not isinstance(input_value, SEQUENCE_TYPES):381 sequence = False382 input_value = [input_value]383 else:384 sequence = True # indicates if a sequence must be returned385 386 valid_values = []387 changed = False388 for element in input_value:389 error = False390 if isinstance(element, STRING_TYPES):391 if element[0] == '{' and element[-1] == '}':392 try:393 valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian394 changed = True395 except ValueError:396 error = True397 elif '-' in element:398 try:399 valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian400 changed = True401 except ValueError:402 error = True403 elif '\\' in element:404 try:405 valid_values.append(UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le) # byte representation, value in little endian406 changed = True407 except ValueError:408 error = True409 elif '-' not in element: # value in little endian410 try:411 valid_values.append(UUID(bytes_le=a2b_hex(element)).bytes_le) # packet representation, value in little endian, converts to little endian412 changed = True413 except ValueError:414 error = True415 if error and (str is bytes): # python2 only assume value is bytes and valid416 valid_values.append(element) # value is untouched, must be in little endian417 elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid uuid418 valid_values.append(element) # value is untouched, must be in little endian419 else:420 return False421 422 if changed:423 # valid_values = [check_backslash(value) for value in valid_values]424 if sequence:425 return valid_values426 else:427 return valid_values[0]428 else:429 return True430 431 432def validate_sid(input_value):433 """434 SID= "S-1-" IdentifierAuthority 1*SubAuthority435 IdentifierAuthority= IdentifierAuthorityDec / IdentifierAuthorityHex436 ; If the identifier authority is < 2^32, the437 ; identifier authority is represented as a decimal438 ; number439 ; If the identifier authority is >= 2^32,440 ; the identifier authority is represented in441 ; hexadecimal442 IdentifierAuthorityDec = 1*10DIGIT443 ; IdentifierAuthorityDec, top level authority of a444 ; security identifier is represented as a decimal number445 IdentifierAuthorityHex = "0x" 12HEXDIG446 ; IdentifierAuthorityHex, the top-level authority of a447 ; security identifier is represented as a hexadecimal number448 SubAuthority= "-" 1*10DIGIT449 ; Sub-Authority is always represented as a decimal number450 ; No leading "0" characters are allowed when IdentifierAuthority451 ; or SubAuthority is represented as a decimal number452 ; All hexadecimal digits must be output in string format,453 ; pre-pended by "0x"454 455 Revision (1 byte): An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.456 SubAuthorityCount (1 byte): An 8-bit unsigned integer that specifies the number of elements in the SubAuthority array. The maximum number of elements allowed is 15.457 IdentifierAuthority (6 bytes): A SID_IDENTIFIER_AUTHORITY structure that indicates the authority under which the SID was created. It describes the entity that created the SID. The Identifier Authority value {0,0,0,0,0,5} denotes SIDs created by the NT SID authority.458 SubAuthority (variable): A variable length array of unsigned 32-bit integers that uniquely identifies a principal relative to the IdentifierAuthority. Its length is determined by SubAuthorityCount.459 460 If you have a SID like S-a-b-c-d-e-f-g-...461 462 Then the bytes are463 a (revision)464 N (number of dashes minus two)465 bbbbbb (six bytes of "b" treated as a 48-bit number in big-endian format)466 cccc (four bytes of "c" treated as a 32-bit number in little-endian format)467 dddd (four bytes of "d" treated as a 32-bit number in little-endian format)468 eeee (four bytes of "e" treated as a 32-bit number in little-endian format)469 ffff (four bytes of "f" treated as a 32-bit number in little-endian format)470 471 """472 if not isinstance(input_value, SEQUENCE_TYPES):473 sequence = False474 input_value = [input_value]475 else:476 sequence = True # indicates if a sequence must be returned477 478 valid_values = []479 changed = False480 for element in input_value:481 if isinstance(element, STRING_TYPES):482 if element.startswith('S-'):483 parts = element.split('-')484 sid_bytes = pack('<q', int(parts[1]))[0:1] # revision number485 sid_bytes += pack('<q', len(parts[3:]))[0:1] # number of sub authorities486 if len(parts[2]) <= 10:487 sid_bytes += pack('>q', int(parts[2]))[2:] # authority (in dec)488 else:489 sid_bytes += pack('>q', int(parts[2], 16))[2:] # authority (in hex)490 for sub_auth in parts[3:]:491 sid_bytes += pack('<q', int(sub_auth))[0:4] # sub-authorities492 valid_values.append(sid_bytes)493 changed = True494 495 if changed:496 # valid_values = [check_backslash(value) for value in valid_values]497 if sequence:498 return valid_values499 else:500 return valid_values[0]501 else:502 return True503 