""" """ # Created on 2016.08.09 # # Author: Giovanni Cannata # # Copyright 2016 - 2018 Giovanni Cannata # # This file is part of ldap3. # # ldap3 is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ldap3 is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ldap3 in the COPYING and COPYING.LESSER files. # If not, see . from binascii import a2b_hex from datetime import datetime from calendar import timegm from uuid import UUID from ... import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, INTEGER_TYPES from .formatters import format_time, format_ad_timestamp from ...utils.conv import to_raw, to_unicode, ldap_escape_to_bytes # Validators return True if value is valid, False if value is not valid, # or a value different from True and False that is a valid value to substitute to the input value def check_type(input_value, value_type): if isinstance(input_value, value_type): return True if isinstance(input_value, SEQUENCE_TYPES): for value in input_value: if not isinstance(value, value_type): return False return True return False # noinspection PyUnusedLocal def always_valid(input_value): return True def validate_generic_single_value(input_value): if not isinstance(input_value, SEQUENCE_TYPES): return True try: # object couldn't have a __len__ method if len(input_value) == 1: return True except Exception: pass return False def validate_zero_and_minus_one(input_value): """Accept -1 only (used by pwdLastSet in AD) """ if not isinstance(input_value, SEQUENCE_TYPES): if input_value == 0 or input_value == '0' or input_value == -1 or input_value == '-1': return True try: # object couldn't have a __len__ method if len(input_value) == 1 and (input_value == 0 or input_value == '0' or input_value == -1 or input_value == '-1'): return True except Exception: pass return False def validate_integer(input_value): if check_type(input_value, (float, bool)): return False if check_type(input_value, INTEGER_TYPES): return True if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] # builds a list of valid int values from decimal import Decimal, InvalidOperation for element in input_value: 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 used value = to_unicode(element) if isinstance(element, bytes) else element decimal_value = Decimal(value) int_value = int(value) if decimal_value == int_value: valid_values.append(int_value) else: return False except (ValueError, TypeError, InvalidOperation): return False if sequence: return valid_values else: return valid_values[0] def validate_bytes(input_value): return check_type(input_value, bytes) def validate_boolean(input_value): # it could be a real bool or the string TRUE or FALSE, # only a single valued is allowed if validate_generic_single_value(input_value): # valid only if a single value or a sequence with a single element if isinstance(input_value, SEQUENCE_TYPES): input_value = input_value[0] if isinstance(input_value, bool): if input_value: return 'TRUE' else: return 'FALSE' if str != bytes and isinstance(input_value, bytes): # python3 try to converts bytes to string input_value = to_unicode(input_value) if isinstance(input_value, STRING_TYPES): if input_value.lower() == 'true': return 'TRUE' elif input_value.lower() == 'false': return 'FALSE' return False def validate_time_with_0_year(input_value): # validates generalized time but accept a 0000 year too # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string element = to_unicode(element) if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time if element.startswith('0000') or isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string valid_values.append(element) else: return False elif isinstance(element, datetime): changed = True if element.tzinfo: # a datetime with a timezone valid_values.append(element.strftime('%Y%m%d%H%M%S%z')) else: # datetime without timezone, assumed local and adjusted to UTC offset = datetime.now() - datetime.utcnow() valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ')) else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True def validate_time(input_value): # if datetime object doesn't have a timezone it's considered local time and is adjusted to UTC if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string element = to_unicode(element) if isinstance(element, STRING_TYPES): # tries to check if it is already be a Generalized Time if isinstance(format_time(to_raw(element)), datetime): # valid Generalized Time string valid_values.append(element) else: return False elif isinstance(element, datetime): changed = True if element.tzinfo: # a datetime with a timezone valid_values.append(element.strftime('%Y%m%d%H%M%S%z')) else: # datetime without timezone, assumed local and adjusted to UTC offset = datetime.now() - datetime.utcnow() valid_values.append((element - offset).strftime('%Y%m%d%H%M%SZ')) else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True def validate_ad_timestamp(input_value): """ Active Directory stores date/time values as the number of 100-nanosecond intervals that have elapsed since the 0 hour on January 1, 1601 till the date/time that is being stored. The time is always stored in Greenwich Mean Time (GMT) in the Active Directory. """ if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if str != bytes and isinstance(element, bytes): # python3 try to converts bytes to string element = to_unicode(element) if isinstance(element, NUMERIC_TYPES): if 0 <= element <= 9223372036854775807: # min and max for the AD timestamp starting from 12:00 AM January 1, 1601 valid_values.append(element) else: return False elif isinstance(element, STRING_TYPES): # tries to check if it is already be a AD timestamp if isinstance(format_ad_timestamp(to_raw(element)), datetime): # valid Generalized Time string valid_values.append(element) else: return False elif isinstance(element, datetime): changed = True if element.tzinfo: # a datetime with a timezone valid_values.append(to_raw((timegm(element.utctimetuple()) + 11644473600) * 10000000, encoding='ascii')) else: # datetime without timezone, assumed local and adjusted to UTC offset = datetime.now() - datetime.utcnow() valid_values.append(to_raw((timegm((element - offset).timetuple()) + 11644473600) * 10000000, encoding='ascii')) else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True def validate_guid(input_value): """ object guid in uuid format (Novell eDirectory) """ if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if isinstance(element, STRING_TYPES): try: valid_values.append(UUID(element).bytes) changed = True except ValueError: # try if the value is an escaped byte sequence try: valid_values.append(UUID(element.replace('\\', '')).bytes) changed = True continue except ValueError: if str != bytes: # python 3 pass else: valid_values.append(element) continue return False elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid valid_values.append(element) else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True def validate_uuid(input_value): """ object entryUUID in uuid format """ if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if isinstance(element, STRING_TYPES): try: valid_values.append(str(UUID(element))) changed = True except ValueError: # try if the value is an escaped byte sequence try: valid_values.append(str(UUID(element.replace('\\', '')))) changed = True continue except ValueError: if str != bytes: # python 3 pass else: valid_values.append(element) continue return False elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid valid_values.append(element) else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True def validate_uuid_le(input_value): """ Active Directory stores objectGUID in uuid_le format, follows RFC4122 and MS-DTYP: "{07039e68-4373-264d-a0a7-07039e684373}": string representation big endian, converted to little endian (with or without brace curles) "689e030773434d26a7a007039e684373": packet representation, already in little endian "\68\9e\03\07\73\43\4d\26\a7\a0\07\03\9e\68\43\73": bytes representation, already in little endian byte sequence: already in little endian """ if not isinstance(input_value, SEQUENCE_TYPES): sequence = False input_value = [input_value] else: sequence = True # indicates if a sequence must be returned valid_values = [] changed = False for element in input_value: if isinstance(element, STRING_TYPES): if element[0] == '{' and element[-1] == '}': valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian changed = True elif '-' in element: valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian changed = True elif '\\' in element: valid_values.append(UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le) # byte representation, value in little endian changed = True elif '-' not in element: # value in little endian valid_values.append(UUID(bytes_le=a2b_hex(element)).bytes_le) # packet representation, value in little endian, converts to little endian changed = True elif isinstance(element, (bytes, bytearray)): # assumes bytes are valid uuid valid_values.append(element) # value is untouched, must be in little endian else: return False if changed: if sequence: return valid_values else: return valid_values[0] else: return True