2025-12-25 upload
This commit is contained in:
436
venv/Lib/site-packages/ldap3/protocol/formatters/formatters.py
Normal file
436
venv/Lib/site-packages/ldap3/protocol/formatters/formatters.py
Normal file
@@ -0,0 +1,436 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.10.28
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 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/>.
|
||||
|
||||
import re
|
||||
|
||||
from binascii import hexlify
|
||||
from uuid import UUID
|
||||
from datetime import datetime, timedelta
|
||||
from ...utils.conv import to_unicode
|
||||
|
||||
from ...core.timezone import OffsetTzInfo
|
||||
|
||||
|
||||
def format_unicode(raw_value):
|
||||
try:
|
||||
if str is not bytes: # Python 3
|
||||
return str(raw_value, 'utf-8', errors='strict')
|
||||
else: # Python 2
|
||||
return unicode(raw_value, 'utf-8', errors='strict')
|
||||
except (TypeError, UnicodeDecodeError):
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_integer(raw_value):
|
||||
try:
|
||||
return int(raw_value)
|
||||
except (TypeError, ValueError): # expected exceptions
|
||||
pass
|
||||
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_binary(raw_value):
|
||||
try:
|
||||
return bytes(raw_value)
|
||||
except TypeError: # expected exceptions
|
||||
pass
|
||||
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_uuid(raw_value):
|
||||
try:
|
||||
return str(UUID(bytes=raw_value))
|
||||
except (TypeError, ValueError):
|
||||
return format_unicode(raw_value)
|
||||
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_uuid_le(raw_value):
|
||||
try:
|
||||
return '{' + str(UUID(bytes_le=raw_value)) + '}'
|
||||
except (TypeError, ValueError):
|
||||
return format_unicode(raw_value)
|
||||
except Exception: # any other exception should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_boolean(raw_value):
|
||||
if raw_value in [b'TRUE', b'true', b'True']:
|
||||
return True
|
||||
if raw_value in [b'FALSE', b'false', b'False']:
|
||||
return False
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_ad_timestamp(raw_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.
|
||||
"""
|
||||
utc_timezone = OffsetTzInfo(0, 'UTC')
|
||||
if raw_value == b'9223372036854775807': # max value to be stored in a 64 bit signed int
|
||||
return datetime.max.replace(tzinfo=utc_timezone) # returns datetime.datetime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=OffsetTzInfo(offset=0, name='UTC'))
|
||||
try:
|
||||
timestamp = int(raw_value)
|
||||
if timestamp < 0: # ad timestamp cannot be negative
|
||||
timestamp = timestamp * -1
|
||||
except Exception:
|
||||
return raw_value
|
||||
|
||||
try:
|
||||
return datetime.fromtimestamp(timestamp / 10000000.0 - 11644473600,
|
||||
tz=utc_timezone) # forces true division in python 2
|
||||
except (OSError, OverflowError, ValueError): # on Windows backwards timestamps are not allowed
|
||||
try:
|
||||
unix_epoch = datetime.fromtimestamp(0, tz=utc_timezone)
|
||||
diff_seconds = timedelta(seconds=timestamp / 10000000.0 - 11644473600)
|
||||
return unix_epoch + diff_seconds
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
try: # uses regular expressions and the timezone class (python3.2 and later)
|
||||
from datetime import timezone
|
||||
|
||||
time_format = re.compile(
|
||||
r'''
|
||||
^
|
||||
(?P<Year>[0-9]{4})
|
||||
(?P<Month>0[1-9]|1[0-2])
|
||||
(?P<Day>0[1-9]|[12][0-9]|3[01])
|
||||
(?P<Hour>[01][0-9]|2[0-3])
|
||||
(?:
|
||||
(?P<Minute>[0-5][0-9])
|
||||
(?P<Second>[0-5][0-9]|60)?
|
||||
)?
|
||||
(?:
|
||||
[.,]
|
||||
(?P<Fraction>[0-9]+)
|
||||
)?
|
||||
(?:
|
||||
Z
|
||||
|
|
||||
(?:
|
||||
(?P<Offset>[+-])
|
||||
(?P<OffHour>[01][0-9]|2[0-3])
|
||||
(?P<OffMinute>[0-5][0-9])?
|
||||
)
|
||||
)
|
||||
$
|
||||
''',
|
||||
re.VERBOSE
|
||||
)
|
||||
|
||||
|
||||
def format_time(raw_value):
|
||||
try:
|
||||
match = time_format.fullmatch(to_unicode(raw_value))
|
||||
if match is None:
|
||||
return raw_value
|
||||
matches = match.groupdict()
|
||||
|
||||
offset = timedelta(
|
||||
hours=int(matches['OffHour'] or 0),
|
||||
minutes=int(matches['OffMinute'] or 0)
|
||||
)
|
||||
|
||||
if matches['Offset'] == '-':
|
||||
offset *= -1
|
||||
|
||||
# Python does not support leap second in datetime (!)
|
||||
if matches['Second'] == '60':
|
||||
matches['Second'] = '59'
|
||||
|
||||
# According to RFC, fraction may be applied to an Hour/Minute (!)
|
||||
fraction = float('0.' + (matches['Fraction'] or '0'))
|
||||
|
||||
if matches['Minute'] is None:
|
||||
fraction *= 60
|
||||
minute = int(fraction)
|
||||
fraction -= minute
|
||||
else:
|
||||
minute = int(matches['Minute'])
|
||||
|
||||
if matches['Second'] is None:
|
||||
fraction *= 60
|
||||
second = int(fraction)
|
||||
fraction -= second
|
||||
else:
|
||||
second = int(matches['Second'])
|
||||
|
||||
microseconds = int(fraction * 1000000)
|
||||
|
||||
return datetime(
|
||||
int(matches['Year']),
|
||||
int(matches['Month']),
|
||||
int(matches['Day']),
|
||||
int(matches['Hour']),
|
||||
minute,
|
||||
second,
|
||||
microseconds,
|
||||
timezone(offset),
|
||||
)
|
||||
except Exception: # exceptions should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
return raw_value
|
||||
|
||||
except ImportError:
|
||||
def format_time(raw_value):
|
||||
"""
|
||||
From RFC4517:
|
||||
A value of the Generalized Time syntax is a character string
|
||||
representing a date and time. The LDAP-specific encoding of a value
|
||||
of this syntax is a restriction of the format defined in [ISO8601],
|
||||
and is described by the following ABNF:
|
||||
|
||||
GeneralizedTime = century year month day hour
|
||||
[ minute [ second / leap-second ] ]
|
||||
[ fraction ]
|
||||
g-time-zone
|
||||
|
||||
century = 2(%x30-39) ; "00" to "99"
|
||||
year = 2(%x30-39) ; "00" to "99"
|
||||
month = ( %x30 %x31-39 ) ; "01" (January) to "09"
|
||||
/ ( %x31 %x30-32 ) ; "10" to "12"
|
||||
day = ( %x30 %x31-39 ) ; "01" to "09"
|
||||
/ ( %x31-32 %x30-39 ) ; "10" to "29"
|
||||
/ ( %x33 %x30-31 ) ; "30" to "31"
|
||||
hour = ( %x30-31 %x30-39 ) / ( %x32 %x30-33 ) ; "00" to "23"
|
||||
minute = %x30-35 %x30-39 ; "00" to "59"
|
||||
second = ( %x30-35 %x30-39 ) ; "00" to "59"
|
||||
leap-second = ( %x36 %x30 ) ; "60"
|
||||
fraction = ( DOT / COMMA ) 1*(%x30-39)
|
||||
g-time-zone = %x5A ; "Z"
|
||||
/ g-differential
|
||||
g-differential = ( MINUS / PLUS ) hour [ minute ]
|
||||
MINUS = %x2D ; minus sign ("-")
|
||||
"""
|
||||
|
||||
if len(raw_value) < 10 or not all((c in b'0123456789+-,.Z' for c in raw_value)) or (
|
||||
b'Z' in raw_value and not raw_value.endswith(
|
||||
b'Z')): # first ten characters are mandatory and must be numeric or timezone or fraction
|
||||
return raw_value
|
||||
|
||||
# sets position for fixed values
|
||||
year = int(raw_value[0: 4])
|
||||
month = int(raw_value[4: 6])
|
||||
day = int(raw_value[6: 8])
|
||||
hour = int(raw_value[8: 10])
|
||||
minute = 0
|
||||
second = 0
|
||||
microsecond = 0
|
||||
|
||||
remain = raw_value[10:]
|
||||
if remain and remain.endswith(b'Z'): # uppercase 'Z'
|
||||
sep = b'Z'
|
||||
elif b'+' in remain: # timezone can be specified with +hh[mm] or -hh[mm]
|
||||
sep = b'+'
|
||||
elif b'-' in remain:
|
||||
sep = b'-'
|
||||
else: # timezone not specified
|
||||
return raw_value
|
||||
|
||||
time, _, offset = remain.partition(sep)
|
||||
|
||||
if time and (b'.' in time or b',' in time):
|
||||
# fraction time
|
||||
if time[0] in b',.':
|
||||
minute = 6 * int(time[1] if str is bytes else chr(time[1])) # Python 2 / Python 3
|
||||
elif time[2] in b',.':
|
||||
minute = int(raw_value[10: 12])
|
||||
second = 6 * int(time[3] if str is bytes else chr(time[3])) # Python 2 / Python 3
|
||||
elif time[4] in b',.':
|
||||
minute = int(raw_value[10: 12])
|
||||
second = int(raw_value[12: 14])
|
||||
microsecond = 100000 * int(time[5] if str is bytes else chr(time[5])) # Python 2 / Python 3
|
||||
elif len(time) == 2: # mmZ format
|
||||
minute = int(raw_value[10: 12])
|
||||
elif len(time) == 0: # Z format
|
||||
pass
|
||||
elif len(time) == 4: # mmssZ
|
||||
minute = int(raw_value[10: 12])
|
||||
second = int(raw_value[12: 14])
|
||||
else:
|
||||
return raw_value
|
||||
|
||||
if sep == b'Z': # UTC
|
||||
timezone = OffsetTzInfo(0, 'UTC')
|
||||
else: # build timezone
|
||||
try:
|
||||
if len(offset) == 2:
|
||||
timezone_hour = int(offset[:2])
|
||||
timezone_minute = 0
|
||||
elif len(offset) == 4:
|
||||
timezone_hour = int(offset[:2])
|
||||
timezone_minute = int(offset[2:4])
|
||||
else: # malformed timezone
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
return raw_value
|
||||
if timezone_hour > 23 or timezone_minute > 59: # invalid timezone
|
||||
return raw_value
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
timezone = OffsetTzInfo((timezone_hour * 60 + timezone_minute) * (1 if sep == b'+' else -1),
|
||||
'UTC' + str(sep + offset, encoding='utf-8'))
|
||||
else: # Python 2
|
||||
timezone = OffsetTzInfo((timezone_hour * 60 + timezone_minute) * (1 if sep == b'+' else -1),
|
||||
unicode('UTC' + sep + offset, encoding='utf-8'))
|
||||
|
||||
try:
|
||||
return datetime(year=year,
|
||||
month=month,
|
||||
day=day,
|
||||
hour=hour,
|
||||
minute=minute,
|
||||
second=second,
|
||||
microsecond=microsecond,
|
||||
tzinfo=timezone)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
|
||||
|
||||
def format_ad_timedelta(raw_value):
|
||||
"""
|
||||
Convert a negative filetime value to a timedelta.
|
||||
"""
|
||||
# Active Directory stores attributes like "minPwdAge" as a negative
|
||||
# "filetime" timestamp, which is the number of 100-nanosecond intervals that
|
||||
# have elapsed since the 0 hour on January 1, 1601.
|
||||
#
|
||||
# Handle the minimum value that can be stored in a 64 bit signed integer.
|
||||
# See https://docs.microsoft.com/en-us/dotnet/api/system.int64.minvalue
|
||||
# In attributes like "maxPwdAge", this signifies never.
|
||||
if raw_value == b'-9223372036854775808':
|
||||
return timedelta.max
|
||||
# We can reuse format_ad_timestamp to get a datetime object from the
|
||||
# timestamp. Afterwards, we can subtract a datetime representing 0 hour on
|
||||
# January 1, 1601 from the returned datetime to get the timedelta.
|
||||
return format_ad_timestamp(raw_value) - format_ad_timestamp(0)
|
||||
|
||||
|
||||
def format_time_with_0_year(raw_value):
|
||||
try:
|
||||
if raw_value.startswith(b'0000'):
|
||||
return raw_value
|
||||
except Exception:
|
||||
try:
|
||||
if raw_value.startswith('0000'):
|
||||
return raw_value
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return format_time(raw_value)
|
||||
|
||||
|
||||
def format_sid(raw_value):
|
||||
"""
|
||||
SID= "S-1-" IdentifierAuthority 1*SubAuthority
|
||||
IdentifierAuthority= IdentifierAuthorityDec / IdentifierAuthorityHex
|
||||
; If the identifier authority is < 2^32, the
|
||||
; identifier authority is represented as a decimal
|
||||
; number
|
||||
; If the identifier authority is >= 2^32,
|
||||
; the identifier authority is represented in
|
||||
; hexadecimal
|
||||
IdentifierAuthorityDec = 1*10DIGIT
|
||||
; IdentifierAuthorityDec, top level authority of a
|
||||
; security identifier is represented as a decimal number
|
||||
IdentifierAuthorityHex = "0x" 12HEXDIG
|
||||
; IdentifierAuthorityHex, the top-level authority of a
|
||||
; security identifier is represented as a hexadecimal number
|
||||
SubAuthority= "-" 1*10DIGIT
|
||||
; Sub-Authority is always represented as a decimal number
|
||||
; No leading "0" characters are allowed when IdentifierAuthority
|
||||
; or SubAuthority is represented as a decimal number
|
||||
; All hexadecimal digits must be output in string format,
|
||||
; pre-pended by "0x"
|
||||
|
||||
Revision (1 byte): An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
if raw_value.startswith(b'S-1-'):
|
||||
return raw_value
|
||||
except Exception:
|
||||
try:
|
||||
if raw_value.startswith('S-1-'):
|
||||
return raw_value
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if str is not bytes: # Python 3
|
||||
revision = int(raw_value[0])
|
||||
sub_authority_count = int(raw_value[1])
|
||||
identifier_authority = int.from_bytes(raw_value[2:8], byteorder='big')
|
||||
if identifier_authority >= 4294967296: # 2 ^ 32
|
||||
identifier_authority = hex(identifier_authority)
|
||||
|
||||
sub_authority = ''
|
||||
i = 0
|
||||
while i < sub_authority_count:
|
||||
sub_authority += '-' + str(
|
||||
int.from_bytes(raw_value[8 + (i * 4): 12 + (i * 4)], byteorder='little')) # little endian
|
||||
i += 1
|
||||
else: # Python 2
|
||||
revision = int(ord(raw_value[0]))
|
||||
sub_authority_count = int(ord(raw_value[1]))
|
||||
identifier_authority = int(hexlify(raw_value[2:8]), 16)
|
||||
if identifier_authority >= 4294967296: # 2 ^ 32
|
||||
identifier_authority = hex(identifier_authority)
|
||||
|
||||
sub_authority = ''
|
||||
i = 0
|
||||
while i < sub_authority_count:
|
||||
sub_authority += '-' + str(int(hexlify(raw_value[11 + (i * 4): 7 + (i * 4): -1]), 16)) # little endian
|
||||
i += 1
|
||||
return 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority
|
||||
except Exception: # any exception should be investigated, anyway the formatter return the raw_value
|
||||
pass
|
||||
|
||||
return raw_value
|
||||
238
venv/Lib/site-packages/ldap3/protocol/formatters/standard.py
Normal file
238
venv/Lib/site-packages/ldap3/protocol/formatters/standard.py
Normal file
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.10.28
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 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 ... import SEQUENCE_TYPES
|
||||
from .formatters import format_ad_timestamp, format_binary, format_boolean,\
|
||||
format_integer, format_sid, format_time, format_unicode, format_uuid, format_uuid_le, format_time_with_0_year,\
|
||||
format_ad_timedelta
|
||||
from .validators import validate_integer, validate_time, always_valid,\
|
||||
validate_generic_single_value, validate_boolean, validate_ad_timestamp, validate_sid,\
|
||||
validate_uuid_le, validate_uuid, validate_zero_and_minus_one_and_positive_int, validate_guid, validate_time_with_0_year,\
|
||||
validate_ad_timedelta
|
||||
|
||||
# for each syntax can be specified a format function and a input validation function
|
||||
|
||||
standard_formatter = {
|
||||
'1.2.840.113556.1.4.903': (format_binary, None), # Object (DN-binary) - Microsoft
|
||||
'1.2.840.113556.1.4.904': (format_unicode, None), # Object (DN-string) - Microsoft
|
||||
'1.2.840.113556.1.4.905': (format_unicode, None), # String (Teletex) - Microsoft
|
||||
'1.2.840.113556.1.4.906': (format_integer, validate_integer), # Large integer - Microsoft
|
||||
'1.2.840.113556.1.4.907': (format_binary, None), # String (NT-sec-desc) - Microsoft
|
||||
'1.2.840.113556.1.4.1221': (format_binary, None), # Object (OR-name) - Microsoft
|
||||
'1.2.840.113556.1.4.1362': (format_unicode, None), # String (Case) - Microsoft
|
||||
'1.3.6.1.4.1.1466.115.121.1.1': (format_binary, None), # ACI item [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.2': (format_binary, None), # Access point [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.3': (format_unicode, None), # Attribute type description
|
||||
'1.3.6.1.4.1.1466.115.121.1.4': (format_binary, None), # Audio [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.5': (format_binary, None), # Binary [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.6': (format_unicode, None), # Bit String
|
||||
'1.3.6.1.4.1.1466.115.121.1.7': (format_boolean, validate_boolean), # Boolean
|
||||
'1.3.6.1.4.1.1466.115.121.1.8': (format_binary, None), # Certificate [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.9': (format_binary, None), # Certificate List [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.10': (format_binary, None), # Certificate Pair [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.11': (format_unicode, None), # Country String
|
||||
'1.3.6.1.4.1.1466.115.121.1.12': (format_unicode, None), # Distinguished name (DN)
|
||||
'1.3.6.1.4.1.1466.115.121.1.13': (format_binary, None), # Data Quality Syntax [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.14': (format_unicode, None), # Delivery method
|
||||
'1.3.6.1.4.1.1466.115.121.1.15': (format_unicode, None), # Directory string
|
||||
'1.3.6.1.4.1.1466.115.121.1.16': (format_unicode, None), # DIT Content Rule Description
|
||||
'1.3.6.1.4.1.1466.115.121.1.17': (format_unicode, None), # DIT Structure Rule Description
|
||||
'1.3.6.1.4.1.1466.115.121.1.18': (format_binary, None), # DL Submit Permission [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.19': (format_binary, None), # DSA Quality Syntax [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.20': (format_binary, None), # DSE Type [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.21': (format_binary, None), # Enhanced Guide
|
||||
'1.3.6.1.4.1.1466.115.121.1.22': (format_unicode, None), # Facsimile Telephone Number
|
||||
'1.3.6.1.4.1.1466.115.121.1.23': (format_binary, None), # Fax
|
||||
'1.3.6.1.4.1.1466.115.121.1.24': (format_time, validate_time), # Generalized time
|
||||
'1.3.6.1.4.1.1466.115.121.1.25': (format_binary, None), # Guide [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.26': (format_unicode, None), # IA5 string
|
||||
'1.3.6.1.4.1.1466.115.121.1.27': (format_integer, validate_integer), # Integer
|
||||
'1.3.6.1.4.1.1466.115.121.1.28': (format_binary, None), # JPEG
|
||||
'1.3.6.1.4.1.1466.115.121.1.29': (format_binary, None), # Master and Shadow Access Points [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.30': (format_unicode, None), # Matching rule description
|
||||
'1.3.6.1.4.1.1466.115.121.1.31': (format_unicode, None), # Matching rule use description
|
||||
'1.3.6.1.4.1.1466.115.121.1.32': (format_unicode, None), # Mail Preference [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.33': (format_unicode, None), # MHS OR Address [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.34': (format_unicode, None), # Name and optional UID
|
||||
'1.3.6.1.4.1.1466.115.121.1.35': (format_unicode, None), # Name form description
|
||||
'1.3.6.1.4.1.1466.115.121.1.36': (format_unicode, None), # Numeric string
|
||||
'1.3.6.1.4.1.1466.115.121.1.37': (format_unicode, None), # Object class description
|
||||
'1.3.6.1.4.1.1466.115.121.1.38': (format_unicode, None), # OID
|
||||
'1.3.6.1.4.1.1466.115.121.1.39': (format_unicode, None), # Other mailbox
|
||||
'1.3.6.1.4.1.1466.115.121.1.40': (format_binary, None), # Octet string
|
||||
'1.3.6.1.4.1.1466.115.121.1.41': (format_unicode, None), # Postal address
|
||||
'1.3.6.1.4.1.1466.115.121.1.42': (format_binary, None), # Protocol Information [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.43': (format_binary, None), # Presentation Address [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.44': (format_unicode, None), # Printable string
|
||||
'1.3.6.1.4.1.1466.115.121.1.45': (format_binary, None), # Subtree specification [OBSOLETE
|
||||
'1.3.6.1.4.1.1466.115.121.1.46': (format_binary, None), # Supplier Information [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.47': (format_binary, None), # Supplier Or Consumer [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.48': (format_binary, None), # Supplier And Consumer [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.49': (format_binary, None), # Supported Algorithm [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.50': (format_unicode, None), # Telephone number
|
||||
'1.3.6.1.4.1.1466.115.121.1.51': (format_unicode, None), # Teletex terminal identifier
|
||||
'1.3.6.1.4.1.1466.115.121.1.52': (format_unicode, None), # Teletex number
|
||||
'1.3.6.1.4.1.1466.115.121.1.53': (format_time, validate_time), # Utc time (deprecated)
|
||||
'1.3.6.1.4.1.1466.115.121.1.54': (format_unicode, None), # LDAP syntax description
|
||||
'1.3.6.1.4.1.1466.115.121.1.55': (format_binary, None), # Modify rights [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.56': (format_binary, None), # LDAP Schema Definition [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.57': (format_unicode, None), # LDAP Schema Description [OBSOLETE]
|
||||
'1.3.6.1.4.1.1466.115.121.1.58': (format_unicode, None), # Substring assertion
|
||||
'1.3.6.1.1.16.1': (format_uuid, validate_uuid), # UUID
|
||||
'1.3.6.1.1.16.4': (format_uuid, validate_uuid), # entryUUID (RFC 4530)
|
||||
'2.16.840.1.113719.1.1.4.1.501': (format_uuid, validate_guid), # GUID (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.0': (format_binary, None), # Unknown (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.6': (format_unicode, None), # Case Ignore List (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.12': (format_binary, None), # Tagged Data (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.13': (format_binary, None), # Octet List (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.14': (format_unicode, None), # Tagged String (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.15': (format_unicode, None), # Tagged Name And String (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.16': (format_binary, None), # NDS Replica Pointer (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.17': (format_unicode, None), # NDS ACL (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.19': (format_time, validate_time), # NDS Timestamp (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.22': (format_integer, validate_integer), # Counter (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.23': (format_unicode, None), # Tagged Name (Novell)
|
||||
'2.16.840.1.113719.1.1.5.1.25': (format_unicode, None), # Typed Name (Novell)
|
||||
'supportedldapversion': (format_integer, None), # supportedLdapVersion (Microsoft)
|
||||
'octetstring': (format_binary, validate_uuid_le), # octect string (Microsoft)
|
||||
'1.2.840.113556.1.4.2': (format_uuid_le, validate_uuid_le), # objectGUID (Microsoft)
|
||||
'1.2.840.113556.1.4.13': (format_ad_timestamp, validate_ad_timestamp), # builtinCreationTime (Microsoft)
|
||||
'1.2.840.113556.1.4.26': (format_ad_timestamp, validate_ad_timestamp), # creationTime (Microsoft)
|
||||
'1.2.840.113556.1.4.49': (format_ad_timestamp, validate_ad_timestamp), # badPasswordTime (Microsoft)
|
||||
'1.2.840.113556.1.4.51': (format_ad_timestamp, validate_ad_timestamp), # lastLogoff (Microsoft)
|
||||
'1.2.840.113556.1.4.52': (format_ad_timestamp, validate_ad_timestamp), # lastLogon (Microsoft)
|
||||
'1.2.840.113556.1.4.60': (format_ad_timedelta, validate_ad_timedelta), # lockoutDuration (Microsoft)
|
||||
'1.2.840.113556.1.4.61': (format_ad_timedelta, validate_ad_timedelta), # lockOutObservationWindow (Microsoft)
|
||||
'1.2.840.113556.1.4.74': (format_ad_timedelta, validate_ad_timedelta), # maxPwdAge (Microsoft)
|
||||
'1.2.840.113556.1.4.78': (format_ad_timedelta, validate_ad_timedelta), # minPwdAge (Microsoft)
|
||||
'1.2.840.113556.1.4.96': (format_ad_timestamp, validate_zero_and_minus_one_and_positive_int), # pwdLastSet (Microsoft, can be set to -1 only)
|
||||
'1.2.840.113556.1.4.146': (format_sid, validate_sid), # objectSid (Microsoft)
|
||||
'1.2.840.113556.1.4.159': (format_ad_timestamp, validate_ad_timestamp), # accountExpires (Microsoft)
|
||||
'1.2.840.113556.1.4.662': (format_ad_timestamp, validate_ad_timestamp), # lockoutTime (Microsoft)
|
||||
'1.2.840.113556.1.4.1696': (format_ad_timestamp, validate_ad_timestamp), # lastLogonTimestamp (Microsoft)
|
||||
'1.3.6.1.4.1.42.2.27.8.1.17': (format_time_with_0_year, validate_time_with_0_year) # pwdAccountLockedTime (Novell)
|
||||
}
|
||||
|
||||
|
||||
def find_attribute_helpers(attr_type, name, custom_formatter):
|
||||
"""
|
||||
Tries to format following the OIDs info and format_helper specification.
|
||||
Search for attribute oid, then attribute name (can be multiple), then attribute syntax
|
||||
Precedence is:
|
||||
1. attribute name
|
||||
2. attribute oid(from schema)
|
||||
3. attribute names (from oid_info)
|
||||
4. attribute syntax (from schema)
|
||||
Custom formatters can be defined in Server object and have precedence over the standard_formatters
|
||||
If no formatter is found the raw_value is returned as bytes.
|
||||
Attributes defined as SINGLE_VALUE in schema are returned as a single object, otherwise are returned as a list of object
|
||||
Formatter functions can return any kind of object
|
||||
return a tuple (formatter, validator)
|
||||
"""
|
||||
formatter = None
|
||||
if custom_formatter and isinstance(custom_formatter, dict): # if custom formatters are defined they have precedence over the standard formatters
|
||||
if name in custom_formatter: # search for attribute name, as returned by the search operation
|
||||
formatter = custom_formatter[name]
|
||||
|
||||
if not formatter and attr_type and attr_type.oid in custom_formatter: # search for attribute oid as returned by schema
|
||||
formatter = custom_formatter[attr_type.oid]
|
||||
if not formatter and attr_type and attr_type.oid_info:
|
||||
if isinstance(attr_type.oid_info[2], SEQUENCE_TYPES): # search for multiple names defined in oid_info
|
||||
for attr_name in attr_type.oid_info[2]:
|
||||
if attr_name in custom_formatter:
|
||||
formatter = custom_formatter[attr_name]
|
||||
break
|
||||
elif attr_type.oid_info[2] in custom_formatter: # search for name defined in oid_info
|
||||
formatter = custom_formatter[attr_type.oid_info[2]]
|
||||
|
||||
if not formatter and attr_type and attr_type.syntax in custom_formatter: # search for syntax defined in schema
|
||||
formatter = custom_formatter[attr_type.syntax]
|
||||
|
||||
if not formatter and name in standard_formatter: # search for attribute name, as returned by the search operation
|
||||
formatter = standard_formatter[name]
|
||||
|
||||
if not formatter and attr_type and attr_type.oid in standard_formatter: # search for attribute oid as returned by schema
|
||||
formatter = standard_formatter[attr_type.oid]
|
||||
|
||||
if not formatter and attr_type and attr_type.oid_info:
|
||||
if isinstance(attr_type.oid_info[2], SEQUENCE_TYPES): # search for multiple names defined in oid_info
|
||||
for attr_name in attr_type.oid_info[2]:
|
||||
if attr_name in standard_formatter:
|
||||
formatter = standard_formatter[attr_name]
|
||||
break
|
||||
elif attr_type.oid_info[2] in standard_formatter: # search for name defined in oid_info
|
||||
formatter = standard_formatter[attr_type.oid_info[2]]
|
||||
if not formatter and attr_type and attr_type.syntax in standard_formatter: # search for syntax defined in schema
|
||||
formatter = standard_formatter[attr_type.syntax]
|
||||
|
||||
if formatter is None:
|
||||
return None, None
|
||||
|
||||
return formatter
|
||||
|
||||
|
||||
def format_attribute_values(schema, name, values, custom_formatter):
|
||||
if not values: # RFCs states that attributes must always have values, but a flaky server returns empty values too
|
||||
return []
|
||||
|
||||
if not isinstance(values, SEQUENCE_TYPES):
|
||||
values = [values]
|
||||
|
||||
if schema and schema.attribute_types and name in schema.attribute_types:
|
||||
attr_type = schema.attribute_types[name]
|
||||
else:
|
||||
attr_type = None
|
||||
|
||||
attribute_helpers = find_attribute_helpers(attr_type, name, custom_formatter)
|
||||
if not isinstance(attribute_helpers, tuple): # custom formatter
|
||||
formatter = attribute_helpers
|
||||
else:
|
||||
formatter = format_unicode if not attribute_helpers[0] else attribute_helpers[0]
|
||||
|
||||
formatted_values = [formatter(raw_value) for raw_value in values] # executes formatter
|
||||
if formatted_values:
|
||||
return formatted_values[0] if (attr_type and attr_type.single_value) else formatted_values
|
||||
else: # RFCs states that attributes must always have values, but AD return empty values in DirSync
|
||||
return []
|
||||
|
||||
|
||||
def find_attribute_validator(schema, name, custom_validator):
|
||||
if schema and schema.attribute_types and name in schema.attribute_types:
|
||||
attr_type = schema.attribute_types[name]
|
||||
else:
|
||||
attr_type = None
|
||||
|
||||
attribute_helpers = find_attribute_helpers(attr_type, name, custom_validator)
|
||||
if not isinstance(attribute_helpers, tuple): # custom validator
|
||||
validator = attribute_helpers
|
||||
else:
|
||||
if not attribute_helpers[1]:
|
||||
if attr_type and attr_type.single_value:
|
||||
validator = validate_generic_single_value # validate only single value
|
||||
else:
|
||||
validator = always_valid # unknown syntax, accepts single and multi value
|
||||
else:
|
||||
validator = attribute_helpers[1]
|
||||
return validator
|
||||
502
venv/Lib/site-packages/ldap3/protocol/formatters/validators.py
Normal file
502
venv/Lib/site-packages/ldap3/protocol/formatters/validators.py
Normal file
@@ -0,0 +1,502 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.08.09
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2020 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, hexlify
|
||||
from datetime import datetime
|
||||
from calendar import timegm
|
||||
from uuid import UUID
|
||||
from struct import pack
|
||||
|
||||
|
||||
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, escape_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_backslash(value):
|
||||
if isinstance(value, (bytearray, bytes)):
|
||||
if b'\\' in value:
|
||||
value = value.replace(b'\\', b'\\5C')
|
||||
elif isinstance(value, STRING_TYPES):
|
||||
if '\\' in value:
|
||||
value = value.replace('\\', '\\5C')
|
||||
return 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_and_positive_int(input_value):
|
||||
"""Accept -1 and 0 only (used by pwdLastSet in AD)
|
||||
"""
|
||||
if not isinstance(input_value, SEQUENCE_TYPES):
|
||||
if isinstance(input_value, NUMERIC_TYPES) or isinstance(input_value, STRING_TYPES):
|
||||
return True if int(input_value) >= -1 else False
|
||||
return False
|
||||
else:
|
||||
if len(input_value) == 1 and (isinstance(input_value[0], NUMERIC_TYPES) or isinstance(input_value[0], STRING_TYPES)):
|
||||
return True if int(input_value[0]) >= -1 else False
|
||||
|
||||
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 is not 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 is not 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 is not 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 is not 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_ad_timedelta(input_value):
|
||||
"""
|
||||
Should be validated like an AD timestamp except that since it is a time
|
||||
delta, it is stored as a negative number.
|
||||
"""
|
||||
if not isinstance(input_value, INTEGER_TYPES) or input_value > 0:
|
||||
return False
|
||||
return validate_ad_timestamp(input_value * -1)
|
||||
|
||||
|
||||
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 ldap byte sequence
|
||||
try:
|
||||
x = ldap_escape_to_bytes(element)
|
||||
valid_values.append(UUID(bytes=x).bytes)
|
||||
changed = True
|
||||
continue
|
||||
except ValueError:
|
||||
if str is not 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:
|
||||
# valid_values = [check_backslash(value) for value in valid_values]
|
||||
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 is not 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:
|
||||
# valid_values = [check_backslash(value) for value in valid_values]
|
||||
if sequence:
|
||||
return valid_values
|
||||
else:
|
||||
return valid_values[0]
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_uuid_le(input_value):
|
||||
r"""
|
||||
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:
|
||||
error = False
|
||||
if isinstance(element, STRING_TYPES):
|
||||
if element[0] == '{' and element[-1] == '}':
|
||||
try:
|
||||
valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian
|
||||
changed = True
|
||||
except ValueError:
|
||||
error = True
|
||||
elif '-' in element:
|
||||
try:
|
||||
valid_values.append(UUID(hex=element).bytes_le) # string representation, value in big endian, converts to little endian
|
||||
changed = True
|
||||
except ValueError:
|
||||
error = True
|
||||
elif '\\' in element:
|
||||
try:
|
||||
valid_values.append(UUID(bytes_le=ldap_escape_to_bytes(element)).bytes_le) # byte representation, value in little endian
|
||||
changed = True
|
||||
except ValueError:
|
||||
error = True
|
||||
elif '-' not in element: # value in little endian
|
||||
try:
|
||||
valid_values.append(UUID(bytes_le=a2b_hex(element)).bytes_le) # packet representation, value in little endian, converts to little endian
|
||||
changed = True
|
||||
except ValueError:
|
||||
error = True
|
||||
if error and (str is bytes): # python2 only assume value is bytes and valid
|
||||
valid_values.append(element) # value is untouched, must be in little endian
|
||||
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:
|
||||
# valid_values = [check_backslash(value) for value in valid_values]
|
||||
if sequence:
|
||||
return valid_values
|
||||
else:
|
||||
return valid_values[0]
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def validate_sid(input_value):
|
||||
"""
|
||||
SID= "S-1-" IdentifierAuthority 1*SubAuthority
|
||||
IdentifierAuthority= IdentifierAuthorityDec / IdentifierAuthorityHex
|
||||
; If the identifier authority is < 2^32, the
|
||||
; identifier authority is represented as a decimal
|
||||
; number
|
||||
; If the identifier authority is >= 2^32,
|
||||
; the identifier authority is represented in
|
||||
; hexadecimal
|
||||
IdentifierAuthorityDec = 1*10DIGIT
|
||||
; IdentifierAuthorityDec, top level authority of a
|
||||
; security identifier is represented as a decimal number
|
||||
IdentifierAuthorityHex = "0x" 12HEXDIG
|
||||
; IdentifierAuthorityHex, the top-level authority of a
|
||||
; security identifier is represented as a hexadecimal number
|
||||
SubAuthority= "-" 1*10DIGIT
|
||||
; Sub-Authority is always represented as a decimal number
|
||||
; No leading "0" characters are allowed when IdentifierAuthority
|
||||
; or SubAuthority is represented as a decimal number
|
||||
; All hexadecimal digits must be output in string format,
|
||||
; pre-pended by "0x"
|
||||
|
||||
Revision (1 byte): An 8-bit unsigned integer that specifies the revision level of the SID. This value MUST be set to 0x01.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
If you have a SID like S-a-b-c-d-e-f-g-...
|
||||
|
||||
Then the bytes are
|
||||
a (revision)
|
||||
N (number of dashes minus two)
|
||||
bbbbbb (six bytes of "b" treated as a 48-bit number in big-endian format)
|
||||
cccc (four bytes of "c" treated as a 32-bit number in little-endian format)
|
||||
dddd (four bytes of "d" treated as a 32-bit number in little-endian format)
|
||||
eeee (four bytes of "e" treated as a 32-bit number in little-endian format)
|
||||
ffff (four bytes of "f" treated as a 32-bit number in little-endian 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):
|
||||
if element.startswith('S-'):
|
||||
parts = element.split('-')
|
||||
sid_bytes = pack('<q', int(parts[1]))[0:1] # revision number
|
||||
sid_bytes += pack('<q', len(parts[3:]))[0:1] # number of sub authorities
|
||||
if len(parts[2]) <= 10:
|
||||
sid_bytes += pack('>q', int(parts[2]))[2:] # authority (in dec)
|
||||
else:
|
||||
sid_bytes += pack('>q', int(parts[2], 16))[2:] # authority (in hex)
|
||||
for sub_auth in parts[3:]:
|
||||
sid_bytes += pack('<q', int(sub_auth))[0:4] # sub-authorities
|
||||
valid_values.append(sid_bytes)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
# valid_values = [check_backslash(value) for value in valid_values]
|
||||
if sequence:
|
||||
return valid_values
|
||||
else:
|
||||
return valid_values[0]
|
||||
else:
|
||||
return True
|
||||
Reference in New Issue
Block a user