1"""
2"""
3
4# Created on 2013.07.24
5#
6# Author: Giovanni Cannata
7#
8# Copyright 2013 - 2020 Giovanni Cannata
9#
10# This file is part of ldap3.
11#
12# ldap3 is free software: you can redistribute it and/or modify
13# it under the terms of the GNU Lesser General Public License as published
14# by the Free Software Foundation, either version 3 of the License, or
15# (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 of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20# GNU Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public License
23# along with ldap3 in the COPYING and COPYING.LESSER files.
24# If not, see <http://www.gnu.org/licenses/>.
25from pyasn1.error import PyAsn1Error
26
27from .. import SEQUENCE_TYPES, STRING_TYPES, get_config_parameter
28from ..core.exceptions import LDAPControlError, LDAPAttributeError, LDAPObjectClassError, LDAPInvalidValueError
29from ..protocol.rfc4511 import Controls, Control
30from ..utils.conv import to_raw, to_unicode, escape_filter_chars, is_filter_escaped
31from ..protocol.formatters.standard import find_attribute_validator
32
33
34def attribute_to_dict(attribute):
35    try:
36        return {'type': str(attribute['type']), 'values': [str(val) for val in attribute['vals']]}
37    except PyAsn1Error:  # invalid encoding, return bytes value
38        return {'type': str(attribute['type']), 'values': [bytes(val) for val in attribute['vals']]}
39
40
41def attributes_to_dict(attributes):
42    attributes_dict = dict()
43    for attribute in attributes:
44        attribute_dict = attribute_to_dict(attribute)
45        attributes_dict[attribute_dict['type']] = attribute_dict['values']
46    return attributes_dict
47
48
49def referrals_to_list(referrals):
50    if isinstance(referrals, list):
51        return [str(referral) for referral in referrals if referral] if referrals else None
52    else:
53        return [str(referral) for referral in referrals if referral] if referrals is not None and referrals.hasValue() else None
54
55
56def search_refs_to_list(search_refs):
57    return [str(search_ref) for search_ref in search_refs if search_ref] if search_refs else None
58
59
60def search_refs_to_list_fast(search_refs):
61    return [to_unicode(search_ref) for search_ref in search_refs if search_ref] if search_refs else None
62
63
64def sasl_to_dict(sasl):
65    return {'mechanism': str(sasl['mechanism']), 'credentials':  bytes(sasl['credentials']) if sasl['credentials'] is not None and sasl['credentials'].hasValue() else None}
66
67
68def authentication_choice_to_dict(authentication_choice):
69    return {'simple': str(authentication_choice['simple']) if authentication_choice.getName() == 'simple' else None, 'sasl': sasl_to_dict(authentication_choice['sasl']) if authentication_choice.getName() == 'sasl' else None}
70
71
72def partial_attribute_to_dict(modification):
73    try:
74        return {'type': str(modification['type']), 'value': [str(value) for value in modification['vals']]}
75    except PyAsn1Error:  # invalid encoding, return bytes value
76        return {'type': str(modification['type']), 'value': [bytes(value) for value in modification['vals']]}
77
78
79def change_to_dict(change):
80    return {'operation': int(change['operation']), 'attribute': partial_attribute_to_dict(change['modification'])}
81
82
83def changes_to_list(changes):
84    return [change_to_dict(change) for change in changes]
85
86
87def attributes_to_list(attributes):
88    return [str(attribute) for attribute in attributes]
89
90
91def ava_to_dict(ava):
92    try:
93        return {'attribute': str(ava['attributeDesc']), 'value': escape_filter_chars(str(ava['assertionValue']))}
94    except Exception:  # invalid encoding, return bytes value
95        try:
96            return {'attribute': str(ava['attributeDesc']), 'value': escape_filter_chars(bytes(ava['assertionValue']))}
97        except Exception:
98            return {'attribute': str(ava['attributeDesc']), 'value': bytes(ava['assertionValue'])}
99
100
101def substring_to_dict(substring):
102    return {'initial': substring['initial'] if substring['initial'] else '', 'any': [middle for middle in substring['any']] if substring['any'] else '', 'final': substring['final'] if substring['final'] else ''}
103
104
105def prepare_changes_for_request(changes):
106    prepared = dict()
107    for change in changes:
108        attribute_name = change['attribute']['type']
109        if attribute_name not in prepared:
110            prepared[attribute_name] = []
111        prepared[attribute_name].append((change['operation'], change['attribute']['value']))
112    return prepared
113
114
115def build_controls_list(controls):
116    """controls is a sequence of Control() or sequences
117    each sequence must have 3 elements: the control OID, the criticality, the value
118    criticality must be a boolean
119    """
120
121    if not controls:
122        return None
123
124    if not isinstance(controls, SEQUENCE_TYPES):
125        raise LDAPControlError('controls must be a sequence')
126
127    built_controls = Controls()
128    for idx, control in enumerate(controls):
129        if isinstance(control, Control):
130            built_controls.setComponentByPosition(idx, control)
131        elif len(control) == 3 and isinstance(control[1], bool):
132            built_control = Control()
133            built_control['controlType'] = control[0]
134            built_control['criticality'] = control[1]
135            if control[2] is not None:
136                built_control['controlValue'] = control[2]
137            built_controls.setComponentByPosition(idx, built_control)
138        else:
139            raise LDAPControlError('control must be a sequence of 3 elements: controlType, criticality (boolean) and controlValue (None if not provided)')
140
141    return built_controls
142
143
144def validate_assertion_value(schema, name, value, auto_escape, auto_encode, validator, check_names):
145    value = to_unicode(value)
146    if auto_escape:
147        if '\\' in value and not is_filter_escaped(value):
148            value = escape_filter_chars(value)
149    value = validate_attribute_value(schema, name, value, auto_encode, validator=validator, check_names=check_names)
150    return value
151
152
153def validate_attribute_value(schema, name, value, auto_encode, validator=None, check_names=False):
154    conf_classes_excluded_from_check = [v.lower() for v in get_config_parameter('CLASSES_EXCLUDED_FROM_CHECK')]
155    conf_attributes_excluded_from_check = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')]
156    conf_utf8_syntaxes = get_config_parameter('UTF8_ENCODED_SYNTAXES')
157    conf_utf8_types = [v.lower() for v in get_config_parameter('UTF8_ENCODED_TYPES')]
158    if schema and schema.attribute_types:
159        if ';' in name:
160            name = name.split(';')[0]
161        if check_names and schema.object_classes and name.lower() == 'objectclass':
162            if to_unicode(value).lower() not in conf_classes_excluded_from_check and to_unicode(value) not in schema.object_classes:
163                raise LDAPObjectClassError('invalid class in objectClass attribute: ' + str(value))
164        elif check_names and name not in schema.attribute_types and name.lower() not in conf_attributes_excluded_from_check:
165            raise LDAPAttributeError('invalid attribute ' + name)
166        else:  # try standard validators
167            validator = find_attribute_validator(schema, name, validator)
168            validated = validator(value)
169            if validated is False:
170                try:  # checks if the value is a byte value erroneously converted to a string (as "b'1234'"), this is a common case in Python 3 when encoding is not specified
171                    if value[0:2] == "b'" and value [-1] == "'":
172                        value = to_raw(value[2:-1])
173                        validated = validator(value)
174                except Exception:
175                    raise LDAPInvalidValueError('value \'%s\' non valid for attribute \'%s\'' % (value, name))
176            if validated is False:
177                raise LDAPInvalidValueError('value \'%s\' non valid for attribute \'%s\'' % (value, name))
178            elif validated is not True:  # a valid LDAP value equivalent to the actual value
179                value = validated
180        # converts to utf-8 for well known Unicode LDAP syntaxes
181        if auto_encode and ((name in schema.attribute_types and schema.attribute_types[name].syntax in conf_utf8_syntaxes) or name.lower() in conf_utf8_types):
182            value = to_unicode(value)  # tries to convert from local encoding to Unicode
183    return to_raw(value)
184
185
186def prepare_filter_for_sending(raw_string):
187    i = 0
188    ints = []
189    raw_string = to_raw(raw_string)
190    while i < len(raw_string):
191        if (raw_string[i] == 92 or raw_string[i] == '\\') and i < len(raw_string) - 2:  # 92 (0x5C) is backslash
192            try:
193                ints.append(int(raw_string[i + 1: i + 3], 16))
194                i += 2
195            except ValueError:  # not an ldap escaped value, sends as is
196                ints.append(92)  # adds backslash
197        else:
198            if str is not bytes:  # Python 3
199                ints.append(raw_string[i])
200            else:  # Python 2
201                ints.append(ord(raw_string[i]))
202        i += 1
203
204    if str is not bytes:  # Python 3
205        return bytes(ints)
206    else:  # Python 2
207        return ''.join(chr(x) for x in ints)
208
209
210def prepare_for_sending(raw_string):
211    return to_raw(raw_string) if isinstance(raw_string, STRING_TYPES) else raw_string
212