123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- """
- """
-
- # 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 <http://www.gnu.org/licenses/>.
- 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
|