2025-12-25 upload

This commit is contained in:
“shengyudong”
2025-12-25 11:16:59 +08:00
commit 322ac74336
2241 changed files with 639966 additions and 0 deletions

View File

@@ -0,0 +1,334 @@
"""
"""
# Created on 2014.04.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 os import linesep
from .. import SUBTREE, DEREF_ALWAYS, ALL_ATTRIBUTES, DEREF_NEVER
from .microsoft.dirSync import DirSync
from .microsoft.modifyPassword import ad_modify_password
from .microsoft.unlockAccount import ad_unlock_account
from .microsoft.addMembersToGroups import ad_add_members_to_groups
from .microsoft.removeMembersFromGroups import ad_remove_members_from_groups
from .microsoft.persistentSearch import ADPersistentSearch
from .novell.partition_entry_count import PartitionEntryCount
from .novell.replicaInfo import ReplicaInfo
from .novell.listReplicas import ListReplicas
from .novell.getBindDn import GetBindDn
from .novell.nmasGetUniversalPassword import NmasGetUniversalPassword
from .novell.nmasSetUniversalPassword import NmasSetUniversalPassword
from .novell.startTransaction import StartTransaction
from .novell.endTransaction import EndTransaction
from .novell.addMembersToGroups import edir_add_members_to_groups
from .novell.removeMembersFromGroups import edir_remove_members_from_groups
from .novell.checkGroupsMemberships import edir_check_groups_memberships
from .standard.whoAmI import WhoAmI
from .standard.modifyPassword import ModifyPassword
from .standard.PagedSearch import paged_search_generator, paged_search_accumulator
from .standard.PersistentSearch import PersistentSearch
class ExtendedOperationContainer(object):
def __init__(self, connection):
self._connection = connection
def __repr__(self):
return linesep.join([' ' + element for element in dir(self) if element[0] != '_'])
def __str__(self):
return self.__repr__()
class StandardExtendedOperations(ExtendedOperationContainer):
def who_am_i(self, controls=None):
return WhoAmI(self._connection,
controls).send()
def modify_password(self,
user=None,
old_password=None,
new_password=None,
hash_algorithm=None,
salt=None,
controls=None):
return ModifyPassword(self._connection,
user,
old_password,
new_password,
hash_algorithm,
salt,
controls).send()
def paged_search(self,
search_base,
search_filter,
search_scope=SUBTREE,
dereference_aliases=DEREF_ALWAYS,
attributes=None,
size_limit=0,
time_limit=0,
types_only=False,
get_operational_attributes=False,
controls=None,
paged_size=100,
paged_criticality=False,
generator=True):
if generator:
return paged_search_generator(self._connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
types_only,
get_operational_attributes,
controls,
paged_size,
paged_criticality)
else:
return paged_search_accumulator(self._connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
types_only,
get_operational_attributes,
controls,
paged_size,
paged_criticality)
def persistent_search(self,
search_base='',
search_filter='(objectclass=*)',
search_scope=SUBTREE,
dereference_aliases=DEREF_NEVER,
attributes=ALL_ATTRIBUTES,
size_limit=0,
time_limit=0,
controls=None,
changes_only=True,
show_additions=True,
show_deletions=True,
show_modifications=True,
show_dn_modifications=True,
notifications=True,
streaming=True,
callback=None
):
events_type = 0
if show_additions:
events_type += 1
if show_deletions:
events_type += 2
if show_modifications:
events_type += 4
if show_dn_modifications:
events_type += 8
if callback:
streaming = False
return PersistentSearch(self._connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
controls,
changes_only,
events_type,
notifications,
streaming,
callback)
def funnel_search(self,
search_base='',
search_filter='',
search_scope=SUBTREE,
dereference_aliases=DEREF_NEVER,
attributes=ALL_ATTRIBUTES,
size_limit=0,
time_limit=0,
controls=None,
streaming=False,
callback=None
):
return PersistentSearch(self._connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
controls,
None,
None,
None,
streaming,
callback)
class NovellExtendedOperations(ExtendedOperationContainer):
def get_bind_dn(self, controls=None):
return GetBindDn(self._connection,
controls).send()
def get_universal_password(self, user, controls=None):
return NmasGetUniversalPassword(self._connection,
user,
controls).send()
def set_universal_password(self, user, new_password=None, controls=None):
return NmasSetUniversalPassword(self._connection,
user,
new_password,
controls).send()
def list_replicas(self, server_dn, controls=None):
return ListReplicas(self._connection,
server_dn,
controls).send()
def partition_entry_count(self, partition_dn, controls=None):
return PartitionEntryCount(self._connection,
partition_dn,
controls).send()
def replica_info(self, server_dn, partition_dn, controls=None):
return ReplicaInfo(self._connection,
server_dn,
partition_dn,
controls).send()
def start_transaction(self, controls=None):
return StartTransaction(self._connection,
controls).send()
def end_transaction(self, commit=True, controls=None): # attach the groupingControl to commit, None to abort transaction
return EndTransaction(self._connection,
commit,
controls).send()
def add_members_to_groups(self, members, groups, fix=True, transaction=True):
return edir_add_members_to_groups(self._connection,
members_dn=members,
groups_dn=groups,
fix=fix,
transaction=transaction)
def remove_members_from_groups(self, members, groups, fix=True, transaction=True):
return edir_remove_members_from_groups(self._connection,
members_dn=members,
groups_dn=groups,
fix=fix,
transaction=transaction)
def check_groups_memberships(self, members, groups, fix=False, transaction=True):
return edir_check_groups_memberships(self._connection,
members_dn=members,
groups_dn=groups,
fix=fix,
transaction=transaction)
class MicrosoftExtendedOperations(ExtendedOperationContainer):
def dir_sync(self,
sync_base,
sync_filter='(objectclass=*)',
attributes=ALL_ATTRIBUTES,
cookie=None,
object_security=False,
ancestors_first=True,
public_data_only=False,
incremental_values=True,
max_length=2147483647,
hex_guid=False):
return DirSync(self._connection,
sync_base=sync_base,
sync_filter=sync_filter,
attributes=attributes,
cookie=cookie,
object_security=object_security,
ancestors_first=ancestors_first,
public_data_only=public_data_only,
incremental_values=incremental_values,
max_length=max_length,
hex_guid=hex_guid)
def modify_password(self, user, new_password, old_password=None, controls=None):
return ad_modify_password(self._connection,
user,
new_password,
old_password,
controls)
def unlock_account(self, user):
return ad_unlock_account(self._connection,
user)
def add_members_to_groups(self, members, groups, fix=True):
return ad_add_members_to_groups(self._connection,
members_dn=members,
groups_dn=groups,
fix=fix)
def remove_members_from_groups(self, members, groups, fix=True):
return ad_remove_members_from_groups(self._connection,
members_dn=members,
groups_dn=groups,
fix=fix)
def persistent_search(self,
search_base='',
search_scope=SUBTREE,
attributes=ALL_ATTRIBUTES,
streaming=True,
callback=None
):
if callback:
streaming = False
return ADPersistentSearch(self._connection,
search_base,
search_scope,
attributes,
streaming,
callback)
class ExtendedOperationsRoot(ExtendedOperationContainer):
def __init__(self, connection):
ExtendedOperationContainer.__init__(self, connection) # calls super
self.standard = StandardExtendedOperations(self._connection)
self.novell = NovellExtendedOperations(self._connection)
self.microsoft = MicrosoftExtendedOperations(self._connection)

View File

@@ -0,0 +1,98 @@
"""
"""
# Created on 2016.12.26
#
# 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 ... import SEQUENCE_TYPES, MODIFY_ADD, BASE, DEREF_NEVER
from ...core.exceptions import LDAPInvalidDnError, LDAPOperationsErrorResult
from ...utils.dn import safe_dn
def ad_add_members_to_groups(connection,
members_dn,
groups_dn,
fix=True,
raise_error=False):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to add to groups
:param groups_dn: the list of groups where members are to be added
:param fix: checks for group existence and already assigned members
:param raise_error: If the operation fails it raises an error instead of returning False
:return: a boolean where True means that the operation was successful and False means an error has happened
Establishes users-groups relations following the Active Directory rules: users are added to the member attribute of groups.
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
"""
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
if connection.check_names: # builds new lists with sanitized dn
members_dn = [safe_dn(member_dn) for member_dn in members_dn]
groups_dn = [safe_dn(group_dn) for group_dn in groups_dn]
error = False
for group in groups_dn:
if fix: # checks for existance of group and for already assigned members
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = connection.response
result = connection.result
if not result['description'] == 'success':
raise LDAPInvalidDnError(group + ' not found')
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
existing_members = [element.lower() for element in existing_members]
else:
existing_members = []
changes = dict()
member_to_add = [element for element in members_dn if element.lower() not in existing_members]
if member_to_add:
changes['member'] = (MODIFY_ADD, member_to_add)
if changes:
result = connection.modify(group, changes)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
result_error_params = ['result', 'description', 'dn', 'message']
if raise_error:
raise LDAPOperationsErrorResult([(k, v) for k, v in result.items() if k in result_error_params])
break
return not error # returns True if no error is raised in the LDAP operations

View File

@@ -0,0 +1,94 @@
"""
"""
# Created on 2015.10.21
#
# Author: Giovanni Cannata
#
# Copyright 2015 - 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 ...core.exceptions import LDAPExtensionError
from ...protocol.microsoft import dir_sync_control, extended_dn_control, show_deleted_control
from ... import SUBTREE, DEREF_NEVER
from ...utils.dn import safe_dn
class DirSync(object):
def __init__(self,
connection,
sync_base,
sync_filter,
attributes,
cookie,
object_security,
ancestors_first,
public_data_only,
incremental_values,
max_length,
hex_guid
):
self.connection = connection
if self.connection.check_names and sync_base:
self. base = safe_dn(sync_base)
else:
self.base = sync_base
self.filter = sync_filter
self.attributes = attributes
self.cookie = cookie
self.object_security = object_security
self.ancestors_first = ancestors_first
self.public_data_only = public_data_only
self.incremental_values = incremental_values
self.max_length = max_length
self.hex_guid = hex_guid
self.more_results = True
def loop(self):
result = self.connection.search(search_base=self.base,
search_filter=self.filter,
search_scope=SUBTREE,
attributes=self.attributes,
dereference_aliases=DEREF_NEVER,
controls=[dir_sync_control(criticality=True,
object_security=self.object_security,
ancestors_first=self.ancestors_first,
public_data_only=self.public_data_only,
incremental_values=self.incremental_values,
max_length=self.max_length, cookie=self.cookie),
extended_dn_control(criticality=False, hex_format=self.hex_guid),
show_deleted_control(criticality=False)]
)
if not self.connection.strategy.sync:
response, result = self.connection.get_response(result)
else:
if self.connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = self.connection.response
result = self.connection.result
if result['description'] == 'success' and 'controls' in result and '1.2.840.113556.1.4.841' in result['controls']:
self.more_results = result['controls']['1.2.840.113556.1.4.841']['value']['more_results']
self.cookie = result['controls']['1.2.840.113556.1.4.841']['value']['cookie']
return response
elif 'controls' in result:
raise LDAPExtensionError('Missing DirSync control in response from server')
else:
raise LDAPExtensionError('error %r in DirSync' % result)

View File

@@ -0,0 +1,75 @@
"""
"""
# Created on 2015.11.27
#
# Author: Giovanni Cannata
#
# Copyright 2015 - 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 MODIFY_REPLACE, MODIFY_DELETE, MODIFY_ADD
from ...utils.log import log, log_enabled, PROTOCOL
from ...core.results import RESULT_SUCCESS
from ...utils.dn import safe_dn
from ...utils.conv import to_unicode
def ad_modify_password(connection, user_dn, new_password, old_password, controls=None):
# old password must be None to reset password with sufficient privileges
if connection.check_names:
user_dn = safe_dn(user_dn)
if str is bytes: # python2, converts to unicode
new_password = to_unicode(new_password)
if old_password:
old_password = to_unicode(old_password)
encoded_new_password = ('"%s"' % new_password).encode('utf-16-le')
if old_password: # normal users must specify old and new password
encoded_old_password = ('"%s"' % old_password).encode('utf-16-le')
result = connection.modify(user_dn,
{'unicodePwd': [(MODIFY_DELETE, [encoded_old_password]),
(MODIFY_ADD, [encoded_new_password])]},
controls)
else: # admin users can reset password without sending the old one
result = connection.modify(user_dn,
{'unicodePwd': [(MODIFY_REPLACE, [encoded_new_password])]},
controls)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
# change successful, returns True
if result['result'] == RESULT_SUCCESS:
return True
# change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
if connection.raise_exceptions:
from ...core.exceptions import LDAPOperationResult
if log_enabled(PROTOCOL):
log(PROTOCOL, 'operation result <%s> for <%s>', result, connection)
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
return False

View File

@@ -0,0 +1,117 @@
"""
"""
# Created on 2016.07.08
#
# 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/>.
try:
from queue import Empty
except ImportError: # Python 2
# noinspection PyUnresolvedReferences
from Queue import Empty
from ...core.exceptions import LDAPExtensionError
from ...utils.dn import safe_dn
from ...protocol.microsoft import persistent_search_control
class ADPersistentSearch(object):
def __init__(self,
connection,
search_base,
search_scope,
attributes,
streaming,
callback
):
if connection.strategy.sync:
raise LDAPExtensionError('Persistent Search needs an asynchronous streaming connection')
if connection.check_names and search_base:
search_base = safe_dn(search_base)
self.connection = connection
self.message_id = None
self.base = search_base
self.scope = search_scope
self.attributes = attributes
self.controls = [persistent_search_control()]
# this is the only filter permitted by AD persistent search
self.filter = '(objectClass=*)'
self.connection.strategy.streaming = streaming
if callback and callable(callback):
self.connection.strategy.callback = callback
elif callback:
raise LDAPExtensionError('callback is not callable')
self.start()
def start(self):
if self.message_id: # persistent search already started
return
if not self.connection.bound:
self.connection.bind()
with self.connection.strategy.async_lock:
self.message_id = self.connection.search(search_base=self.base,
search_filter=self.filter,
search_scope=self.scope,
attributes=self.attributes,
controls=self.controls)
self.connection.strategy.persistent_search_message_id = self.message_id
def stop(self, unbind=True):
self.connection.abandon(self.message_id)
if unbind:
self.connection.unbind()
if self.message_id in self.connection.strategy._responses:
del self.connection.strategy._responses[self.message_id]
if hasattr(self.connection.strategy, '_requests') and self.message_id in self.connection.strategy._requests: # asynchronous strategy has a dict of request that could be returned by get_response()
del self.connection.strategy._requests[self.message_id]
self.connection.strategy.persistent_search_message_id = None
self.message_id = None
def next(self, block=False, timeout=None):
if not self.connection.strategy.streaming and not self.connection.strategy.callback:
try:
return self.connection.strategy.events.get(block, timeout)
except Empty:
return None
raise LDAPExtensionError('Persistent search is not accumulating events in queue')
def funnel(self, block=False, timeout=None):
done = False
while not done:
try:
entry = self.connection.strategy.events.get(block, timeout)
except Empty:
yield None
if entry['type'] == 'searchResEntry':
yield entry
else:
done = True
yield entry

View File

@@ -0,0 +1,99 @@
"""
"""
# Created on 2016.12.26
#
# 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 ...core.exceptions import LDAPInvalidDnError, LDAPOperationsErrorResult
from ... import SEQUENCE_TYPES, MODIFY_DELETE, BASE, DEREF_NEVER
from ...utils.dn import safe_dn
def ad_remove_members_from_groups(connection,
members_dn,
groups_dn,
fix,
raise_error=False):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to remove from groups
:param groups_dn: the list of groups where members are to be removed
:param fix: checks for group existence and existing members
:param raise_error: If the operation fails it raises an error instead of returning False
:return: a boolean where True means that the operation was successful and False means an error has happened
Removes users-groups relations following the Activwe Directory rules: users are removed from groups' member attribute
"""
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
if connection.check_names: # builds new lists with sanitized dn
members_dn = [safe_dn(member_dn) for member_dn in members_dn]
groups_dn = [safe_dn(group_dn) for group_dn in groups_dn]
error = False
for group in groups_dn:
if fix: # checks for existance of group and for already assigned members
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = connection.response
result = connection.result
if not result['description'] == 'success':
raise LDAPInvalidDnError(group + ' not found')
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
else:
existing_members = members_dn
existing_members = [element.lower() for element in existing_members]
changes = dict()
member_to_remove = [element for element in members_dn if element.lower() in existing_members]
if member_to_remove:
changes['member'] = (MODIFY_DELETE, member_to_remove)
if changes:
result = connection.modify(group, changes)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
result_error_params = ['result', 'description', 'dn', 'message']
if raise_error:
raise LDAPOperationsErrorResult([(k, v) for k, v in result.items() if k in result_error_params])
break
return not error

View File

@@ -0,0 +1,57 @@
"""
"""
# Created on 2016.11.01
#
# Author: Giovanni Cannata
#
# Copyright 2015 - 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 MODIFY_REPLACE
from ...utils.log import log, log_enabled, PROTOCOL
from ...core.results import RESULT_SUCCESS
from ...utils.dn import safe_dn
def ad_unlock_account(connection, user_dn, controls=None):
if connection.check_names:
user_dn = safe_dn(user_dn)
result = connection.modify(user_dn, {'lockoutTime': [(MODIFY_REPLACE, ['0'])]}, controls)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
# change successful, returns True
if result['result'] == RESULT_SUCCESS:
return True
# change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
if connection.raise_exceptions:
from ...core.exceptions import LDAPOperationResult
if log_enabled(PROTOCOL):
log(PROTOCOL, 'operation result <%s> for <%s>', result, connection)
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
return result

View File

@@ -0,0 +1,167 @@
"""
"""
# Created on 2016.04.16
#
# 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 ...core.exceptions import LDAPInvalidDnError
from ... import SEQUENCE_TYPES, MODIFY_ADD, BASE, DEREF_NEVER
from ...utils.dn import safe_dn
def edir_add_members_to_groups(connection,
members_dn,
groups_dn,
fix,
transaction):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to add to groups
:param groups_dn: the list of groups where members are to be added
:param fix: checks for inconsistences in the users-groups relation and fixes them
:param transaction: activates an LDAP transaction
:return: a boolean where True means that the operation was successful and False means an error has happened
Establishes users-groups relations following the eDirectory rules: groups are added to securityEquals and groupMembership
attributes in the member object while members are added to member and equivalentToMe attributes in the group object.
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
"""
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
transaction_control = None
error = False
if connection.check_names: # builds new lists with sanitized dn
safe_members_dn = []
safe_groups_dn = []
for member_dn in members_dn:
safe_members_dn.append(safe_dn(member_dn))
for group_dn in groups_dn:
safe_groups_dn.append(safe_dn(group_dn))
members_dn = safe_members_dn
groups_dn = safe_groups_dn
if transaction:
transaction_control = connection.extend.novell.start_transaction()
if not error:
for member in members_dn:
if fix: # checks for existance of member and for already assigned groups
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['securityEquals', 'groupMembership'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
result = connection.result
response = connection.response
if not result['description'] == 'success':
raise LDAPInvalidDnError(member + ' not found')
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
existing_security_equals = [element.lower() for element in existing_security_equals]
existing_group_membership = [element.lower() for element in existing_group_membership]
else:
existing_security_equals = []
existing_group_membership = []
changes = dict()
security_equals_to_add = [element for element in groups_dn if element.lower() not in existing_security_equals]
group_membership_to_add = [element for element in groups_dn if element.lower() not in existing_group_membership]
if security_equals_to_add:
changes['securityEquals'] = (MODIFY_ADD, security_equals_to_add)
if group_membership_to_add:
changes['groupMembership'] = (MODIFY_ADD, group_membership_to_add)
if changes:
result = connection.modify(member, changes, controls=[transaction_control] if transaction else None)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
break
if not error:
for group in groups_dn:
if fix: # checks for existance of group and for already assigned members
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
result = connection.result
response = connection.response
if not result['description'] == 'success':
raise LDAPInvalidDnError(group + ' not found')
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
existing_members = [element.lower() for element in existing_members]
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
else:
existing_members = []
existing_equivalent_to_me = []
changes = dict()
member_to_add = [element for element in members_dn if element.lower() not in existing_members]
equivalent_to_me_to_add = [element for element in members_dn if element.lower() not in existing_equivalent_to_me]
if member_to_add:
changes['member'] = (MODIFY_ADD, member_to_add)
if equivalent_to_me_to_add:
changes['equivalentToMe'] = (MODIFY_ADD, equivalent_to_me_to_add)
if changes:
result = connection.modify(group, changes, controls=[transaction_control] if transaction else None)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
break
if transaction:
if error: # aborts transaction in case of error in the modify operations
result = connection.extend.novell.end_transaction(commit=False, controls=[transaction_control])
else:
result = connection.extend.novell.end_transaction(commit=True, controls=[transaction_control])
if result['description'] != 'success':
error = True
return not error # returns True if no error is raised in the LDAP operations

View File

@@ -0,0 +1,180 @@
"""
"""
# Created on 2016.05.14
#
# 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 .addMembersToGroups import edir_add_members_to_groups
from ...core.exceptions import LDAPInvalidDnError
from ... import SEQUENCE_TYPES, BASE, DEREF_NEVER
from ...utils.dn import safe_dn
def _check_members_have_memberships(connection,
members_dn,
groups_dn):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to add to groups
:param groups_dn: the list of groups where members are to be added
:return: two booleans. The first when True means that all members have membership in all groups, The second when True means that
there are inconsistences in the securityEquals attribute
Checks user's group membership.
Raises LDAPInvalidDNError if member is not found in the DIT.
"""
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
partial = False # True when a member has groupMembership but doesn't have securityEquals
for member in members_dn:
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['groupMembership', 'securityEquals'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
result = connection.result
response = connection.response
if not result['description'] == 'success': # member not found in DIT
raise LDAPInvalidDnError(member + ' not found')
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
existing_security_equals = [element.lower() for element in existing_security_equals]
existing_group_membership = [element.lower() for element in existing_group_membership]
for group in groups_dn:
if group.lower() not in existing_group_membership:
return False, False
if group.lower() not in existing_security_equals:
partial = True
return True, partial
def _check_groups_contain_members(connection,
groups_dn,
members_dn):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to add to groups
:param groups_dn: the list of groups where members are to be added
:return: two booleans. The first when True means that all members have membership in all groups, The second when True means that
there are inconsistences in the EquivalentToMe attribute
Checks if groups have members in their 'member' attribute.
Raises LDAPInvalidDNError if member is not found in the DIT.
"""
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
partial = False # True when a group has member but doesn't have equivalentToMe
for group in groups_dn:
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
result = connection.result
response = connection.response
if not result['description'] == 'success':
raise LDAPInvalidDnError(group + ' not found')
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
existing_members = [element.lower() for element in existing_members]
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
for member in members_dn:
if member.lower() not in existing_members:
return False, False
if member.lower() not in existing_equivalent_to_me:
partial = True
return True, partial
def edir_check_groups_memberships(connection,
members_dn,
groups_dn,
fix,
transaction):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to check
:param groups_dn: the list of groups to check
:param fix: checks for inconsistences in the users-groups relation and fixes them
:param transaction: activates an LDAP transaction when fixing
:return: a boolean where True means that the operation was successful and False means an error has happened
Checks and fixes users-groups relations following the eDirectory rules: groups are checked against 'groupMembership'
attribute in the member object while members are checked against 'member' attribute in the group object.
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
"""
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if connection.check_names: # builds new lists with sanitized dn
safe_members_dn = []
safe_groups_dn = []
for member_dn in members_dn:
safe_members_dn.append(safe_dn(member_dn))
for group_dn in groups_dn:
safe_groups_dn.append(safe_dn(group_dn))
members_dn = safe_members_dn
groups_dn = safe_groups_dn
try:
members_have_memberships, partial_member_security = _check_members_have_memberships(connection, members_dn, groups_dn)
groups_contain_members, partial_group_security = _check_groups_contain_members(connection, groups_dn, members_dn)
except LDAPInvalidDnError:
return False
if not members_have_memberships and not groups_contain_members:
return False
if fix: # fix any inconsistences
if (members_have_memberships and not groups_contain_members) \
or (groups_contain_members and not members_have_memberships) \
or partial_group_security \
or partial_member_security:
for member in members_dn:
for group in groups_dn:
edir_add_members_to_groups(connection, member, group, True, transaction)
return True

View File

@@ -0,0 +1,58 @@
"""
"""
# Created on 2016.04.14
#
# 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 ...extend.operation import ExtendedOperation
from ...protocol.novell import EndGroupTypeRequestValue, EndGroupTypeResponseValue, Sequence
from ...utils.asn1 import decoder
class EndTransaction(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.103.2'
self.response_name = '2.16.840.1.113719.1.27.103.2'
self.request_value = EndGroupTypeRequestValue()
self.asn1_spec = EndGroupTypeResponseValue()
def __init__(self, connection, commit=True, controls=None):
if controls and len(controls) == 1:
group_cookie = decoder.decode(controls[0][2], asn1Spec=Sequence())[0][0] # get the cookie from the built groupingControl
else:
group_cookie = None
controls = None
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if group_cookie:
self.request_value['endGroupCookie'] = group_cookie # transactionGroupingType
if not commit:
self.request_value['endGroupValue'] = '' # an empty endGroupValue means abort transaction
def populate_result(self):
try:
self.result['value'] = self.decoded_response['endGroupValue']
except TypeError:
self.result['value'] = None
def set_response(self):
self.response_value = self.result

View File

@@ -0,0 +1,41 @@
"""
"""
# Created on 2014.04.30
#
# 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 ...protocol.novell import Identity
from ...extend.operation import ExtendedOperation
class GetBindDn(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.100.31'
self.response_name = '2.16.840.1.113719.1.27.100.32'
self.response_attribute = 'identity'
self.asn1_spec = Identity()
def populate_result(self):
try:
self.result['identity'] = str(self.decoded_response) if self.decoded_response else None
except TypeError:
self.result['identity'] = None

View File

@@ -0,0 +1,50 @@
"""
"""
# Created on 2014.07.03
#
# 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 ...extend.operation import ExtendedOperation
from ...protocol.novell import ReplicaList
from ...protocol.rfc4511 import LDAPDN
from ...utils.dn import safe_dn
class ListReplicas(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.100.19'
self.response_name = '2.16.840.1.113719.1.27.100.20'
self.request_value = LDAPDN()
self.asn1_spec = ReplicaList()
self.response_attribute = 'replicas'
def __init__(self, connection, server_dn, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if connection.check_names:
server_dn = safe_dn(server_dn)
self.request_value = LDAPDN(server_dn)
def populate_result(self):
try:
self.result['replicas'] = [str(replica) for replica in self.decoded_response] if self.decoded_response else None
except TypeError:
self.result['replicas'] = None

View File

@@ -0,0 +1,56 @@
"""
"""
# Created on 2014.07.03
#
# 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 ...extend.operation import ExtendedOperation
from ...protocol.novell import NmasGetUniversalPasswordRequestValue, NmasGetUniversalPasswordResponseValue, NMAS_LDAP_EXT_VERSION
from ...utils.dn import safe_dn
class NmasGetUniversalPassword(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.39.42.100.13'
self.response_name = '2.16.840.1.113719.1.39.42.100.14'
self.request_value = NmasGetUniversalPasswordRequestValue()
self.asn1_spec = NmasGetUniversalPasswordResponseValue()
self.response_attribute = 'password'
def __init__(self, connection, user, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if connection.check_names:
user = safe_dn(user)
self.request_value['nmasver'] = NMAS_LDAP_EXT_VERSION
self.request_value['reqdn'] = user
def populate_result(self):
if self.decoded_response:
self.result['nmasver'] = int(self.decoded_response['nmasver'])
self.result['error'] = int(self.decoded_response['err'])
try:
self.result['password'] = str(self.decoded_response['passwd']) if self.decoded_response['passwd'].hasValue() else None
except TypeError:
self.result['password'] = None

View File

@@ -0,0 +1,52 @@
"""
"""
# Created on 2014.07.03
#
# 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 ...extend.operation import ExtendedOperation
from ...protocol.novell import NmasSetUniversalPasswordRequestValue, NmasSetUniversalPasswordResponseValue, NMAS_LDAP_EXT_VERSION
from ...utils.dn import safe_dn
class NmasSetUniversalPassword(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.39.42.100.11'
self.response_name = '2.16.840.1.113719.1.39.42.100.12'
self.request_value = NmasSetUniversalPasswordRequestValue()
self.asn1_spec = NmasSetUniversalPasswordResponseValue()
self.response_attribute = 'password'
def __init__(self, connection, user, new_password, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if connection.check_names and user:
user = safe_dn(user)
self.request_value['nmasver'] = NMAS_LDAP_EXT_VERSION
if user:
self.request_value['reqdn'] = user
if new_password:
self.request_value['new_passwd'] = new_password
def populate_result(self):
self.result['nmasver'] = int(self.decoded_response['nmasver'])
self.result['error'] = int(self.decoded_response['err'])

View File

@@ -0,0 +1,57 @@
"""
"""
# Created on 2014.08.05
#
# 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 pyasn1.type.univ import Integer
from ...core.exceptions import LDAPExtensionError
from ..operation import ExtendedOperation
from ...protocol.rfc4511 import LDAPDN
from ...utils.asn1 import decoder
from ...utils.dn import safe_dn
class PartitionEntryCount(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.100.13'
self.response_name = '2.16.840.1.113719.1.27.100.14'
self.request_value = LDAPDN()
self.response_attribute = 'entry_count'
def __init__(self, connection, partition_dn, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if connection.check_names:
partition_dn = safe_dn(partition_dn)
self.request_value = LDAPDN(partition_dn)
def populate_result(self):
substrate = self.decoded_response
try:
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['entry_count'] = int(decoded)
except Exception:
raise LDAPExtensionError('unable to decode substrate')
if substrate:
raise LDAPExtensionError('unknown substrate remaining')

View File

@@ -0,0 +1,170 @@
"""
"""
# Created on 2016.04.17
#
# 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 ...core.exceptions import LDAPInvalidDnError
from ... import SEQUENCE_TYPES, MODIFY_DELETE, BASE, DEREF_NEVER
from ...utils.dn import safe_dn
def edir_remove_members_from_groups(connection,
members_dn,
groups_dn,
fix,
transaction):
"""
:param connection: a bound Connection object
:param members_dn: the list of members to remove from groups
:param groups_dn: the list of groups where members are to be removed
:param fix: checks for inconsistences in the users-groups relation and fixes them
:param transaction: activates an LDAP transaction
:return: a boolean where True means that the operation was successful and False means an error has happened
Removes users-groups relations following the eDirectory rules: groups are removed from securityEquals and groupMembership
attributes in the member object while members are removed from member and equivalentToMe attributes in the group object.
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
"""
if not isinstance(members_dn, SEQUENCE_TYPES):
members_dn = [members_dn]
if not isinstance(groups_dn, SEQUENCE_TYPES):
groups_dn = [groups_dn]
if connection.check_names: # builds new lists with sanitized dn
safe_members_dn = []
safe_groups_dn = []
for member_dn in members_dn:
safe_members_dn.append(safe_dn(member_dn))
for group_dn in groups_dn:
safe_groups_dn.append(safe_dn(group_dn))
members_dn = safe_members_dn
groups_dn = safe_groups_dn
transaction_control = None
error = False
if transaction:
transaction_control = connection.extend.novell.start_transaction()
if not error:
for member in members_dn:
if fix: # checks for existance of member and for already assigned groups
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['securityEquals', 'groupMembership'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = connection.response
result = connection.result
if not result['description'] == 'success':
raise LDAPInvalidDnError(member + ' not found')
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
else:
existing_security_equals = groups_dn
existing_group_membership = groups_dn
existing_security_equals = [element.lower() for element in existing_security_equals]
existing_group_membership = [element.lower() for element in existing_group_membership]
changes = dict()
security_equals_to_remove = [element for element in groups_dn if element.lower() in existing_security_equals]
group_membership_to_remove = [element for element in groups_dn if element.lower() in existing_group_membership]
if security_equals_to_remove:
changes['securityEquals'] = (MODIFY_DELETE, security_equals_to_remove)
if group_membership_to_remove:
changes['groupMembership'] = (MODIFY_DELETE, group_membership_to_remove)
if changes:
result = connection.modify(member, changes, controls=[transaction_control] if transaction else None)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
break
if not error:
for group in groups_dn:
if fix: # checks for existance of group and for already assigned members
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = connection.response
result = connection.result
if not result['description'] == 'success':
raise LDAPInvalidDnError(group + ' not found')
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
else:
existing_members = members_dn
existing_equivalent_to_me = members_dn
existing_members = [element.lower() for element in existing_members]
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
changes = dict()
member_to_remove = [element for element in members_dn if element.lower() in existing_members]
equivalent_to_me_to_remove = [element for element in members_dn if element.lower() in existing_equivalent_to_me]
if member_to_remove:
changes['member'] = (MODIFY_DELETE, member_to_remove)
if equivalent_to_me_to_remove:
changes['equivalentToMe'] = (MODIFY_DELETE, equivalent_to_me_to_remove)
if changes:
result = connection.modify(group, changes, controls=[transaction_control] if transaction else None)
if not connection.strategy.sync:
_, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, _, _ = result
else:
result = connection.result
if result['description'] != 'success':
error = True
break
if transaction:
if error: # aborts transaction in case of error in the modify operations
result = connection.extend.novell.end_transaction(commit=False, controls=[transaction_control])
else:
result = connection.extend.novell.end_transaction(commit=True, controls=[transaction_control])
if result['description'] != 'success':
error = True
return not error # return True if no error is raised in the LDAP operations

View File

@@ -0,0 +1,79 @@
"""
"""
# Created on 2014.08.07
#
# 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 datetime import datetime
from pyasn1.type.univ import Integer
from ...core.exceptions import LDAPExtensionError
from ...protocol.novell import LDAPDN, ReplicaInfoRequestValue
from ..operation import ExtendedOperation
from ...utils.asn1 import decoder
from ...utils.dn import safe_dn
class ReplicaInfo(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.100.17'
self.response_name = '2.16.840.1.113719.1.27.100.18'
# self.asn1_spec = ReplicaInfoResponseValue()
self.request_value = ReplicaInfoRequestValue()
self.response_attribute = 'partition_dn'
def __init__(self, connection, server_dn, partition_dn, controls=None):
if connection.check_names:
if server_dn:
server_dn = safe_dn(server_dn)
if partition_dn:
partition_dn = safe_dn(partition_dn)
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
self.request_value['server_dn'] = server_dn
self.request_value['partition_dn'] = partition_dn
def populate_result(self):
substrate = self.decoded_response
try:
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['partition_id'] = int(decoded)
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['replica_state'] = int(decoded)
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['modification_time'] = datetime.utcfromtimestamp(int(decoded))
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['purge_time'] = datetime.utcfromtimestamp(int(decoded))
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['local_partition_id'] = int(decoded)
decoded, substrate = decoder.decode(substrate, asn1Spec=LDAPDN())
self.result['partition_dn'] = str(decoded)
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['replica_type'] = int(decoded)
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
self.result['flags'] = int(decoded)
except Exception:
raise LDAPExtensionError('unable to decode substrate')
if substrate:
raise LDAPExtensionError('unknown substrate remaining')

View File

@@ -0,0 +1,56 @@
"""
"""
# Created on 2016.04.14
#
# 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 ...extend.operation import ExtendedOperation
from ...protocol.novell import CreateGroupTypeRequestValue, CreateGroupTypeResponseValue, GroupingControlValue
from ...protocol.controls import build_control
class StartTransaction(ExtendedOperation):
def config(self):
self.request_name = '2.16.840.1.113719.1.27.103.1'
self.response_name = '2.16.840.1.113719.1.27.103.1'
self.request_value = CreateGroupTypeRequestValue()
self.asn1_spec = CreateGroupTypeResponseValue()
def __init__(self, connection, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
self.request_value['createGroupType'] = '2.16.840.1.113719.1.27.103.7' # transactionGroupingType
def populate_result(self):
self.result['cookie'] = int(self.decoded_response['createGroupCookie'])
try:
self.result['value'] = self.decoded_response['createGroupValue']
except TypeError:
self.result['value'] = None
def set_response(self):
try:
grouping_cookie_value = GroupingControlValue()
grouping_cookie_value['groupingCookie'] = self.result['cookie']
self.response_value = build_control('2.16.840.1.113719.1.27.103.7', True, grouping_cookie_value, encode_control_value=True) # groupingControl
except TypeError:
self.response_value = None

View File

@@ -0,0 +1,98 @@
"""
"""
# Created on 2014.07.04
#
# 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 ..core.results import RESULT_SUCCESS
from ..core.exceptions import LDAPExtensionError
from ..utils.asn1 import decoder
class ExtendedOperation(object):
def __init__(self, connection, controls=None):
self.connection = connection
self.decoded_response = None
self.result = None
self.asn1_spec = None # if None the response_value is returned without encoding
self.request_name = None
self.response_name = None
self.request_value = None
self.response_value = None
self.response_attribute = None
self.controls = controls
self.config()
def send(self):
if self.connection.check_names and self.connection.server.info is not None and self.connection.server.info.supported_extensions is not None: # checks if extension is supported
for request_name in self.connection.server.info.supported_extensions:
if request_name[0] == self.request_name:
break
else:
raise LDAPExtensionError('extension not in DSA list of supported extensions')
resp = self.connection.extended(self.request_name, self.request_value, self.controls)
if not self.connection.strategy.sync:
_, result = self.connection.get_response(resp)
else:
if self.connection.strategy.thread_safe:
_, result, _, _ = resp
else:
result = self.connection.result
self.result = result
self.decode_response(result)
self.populate_result()
self.set_response()
return self.response_value
def populate_result(self):
pass
def decode_response(self, response=None):
if not response:
response = self.result
if not response:
return None
if response['result'] not in [RESULT_SUCCESS]:
if self.connection.raise_exceptions:
raise LDAPExtensionError('extended operation error: ' + response['description'] + ' - ' + response['message'])
else:
return None
if not self.response_name or response['responseName'] == self.response_name:
if response['responseValue']:
if self.asn1_spec is not None:
decoded, unprocessed = decoder.decode(response['responseValue'], asn1Spec=self.asn1_spec)
if unprocessed:
raise LDAPExtensionError('error decoding extended response value')
self.decoded_response = decoded
else:
self.decoded_response = response['responseValue']
else:
raise LDAPExtensionError('invalid response name received')
def set_response(self):
self.response_value = self.result[self.response_attribute] if self.result and self.response_attribute in self.result else None
if not self.connection.strategy.thread_safe:
self.connection.response = self.response_value
def config(self):
pass

View File

@@ -0,0 +1,146 @@
"""
"""
# Created on 2014.07.08
#
# 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 SUBTREE, DEREF_ALWAYS
from ...utils.dn import safe_dn
from ...core.results import DO_NOT_RAISE_EXCEPTIONS, RESULT_SIZE_LIMIT_EXCEEDED
from ...core.exceptions import LDAPOperationResult
from ...utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED
def paged_search_generator(connection,
search_base,
search_filter,
search_scope=SUBTREE,
dereference_aliases=DEREF_ALWAYS,
attributes=None,
size_limit=0,
time_limit=0,
types_only=False,
get_operational_attributes=False,
controls=None,
paged_size=100,
paged_criticality=False):
if connection.check_names and search_base:
search_base = safe_dn(search_base)
responses = []
original_connection = None
original_auto_referrals = connection.auto_referrals
connection.auto_referrals = False # disable auto referrals because it cannot handle paged searches
cookie = True # performs search operation at least one time
cachekey = None # for referrals cache
while cookie:
result = connection.search(search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
types_only,
get_operational_attributes,
controls,
paged_size,
paged_criticality,
None if cookie is True else cookie)
if not connection.strategy.sync:
response, result = connection.get_response(result)
else:
if connection.strategy.thread_safe:
_, result, response, _ = result
else:
response = connection.response
result = connection.result
if result['referrals'] and original_auto_referrals: # if rererrals are returned start over the loop with a new connection to the referral
if not original_connection:
original_connection = connection
_, connection, cachekey = connection.strategy.create_referral_connection(result['referrals']) # change connection to a valid referrals
continue
responses.extend(response)
try:
cookie = result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
except KeyError:
cookie = None
if connection.raise_exceptions and result and result['result'] not in DO_NOT_RAISE_EXCEPTIONS:
if log_enabled(PROTOCOL):
log(PROTOCOL, 'paged search operation result <%s> for <%s>', result, connection)
if result['result'] == RESULT_SIZE_LIMIT_EXCEEDED:
while responses:
yield responses.pop()
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
while responses:
yield responses.pop()
if original_connection:
connection = original_connection
if connection.use_referral_cache and cachekey:
connection.strategy.referral_cache[cachekey] = connection
else:
connection.unbind()
connection.auto_referrals = original_auto_referrals
connection.response = None
def paged_search_accumulator(connection,
search_base,
search_filter,
search_scope=SUBTREE,
dereference_aliases=DEREF_ALWAYS,
attributes=None,
size_limit=0,
time_limit=0,
types_only=False,
get_operational_attributes=False,
controls=None,
paged_size=100,
paged_criticality=False):
if connection.check_names and search_base:
search_base = safe_dn(search_base)
responses = []
for response in paged_search_generator(connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
types_only,
get_operational_attributes,
controls,
paged_size,
paged_criticality):
responses.append(response)
connection.response = responses
return responses

View File

@@ -0,0 +1,137 @@
"""
"""
# Created on 2016.07.08
#
# 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/>.
try:
from queue import Empty
except ImportError: # Python 2
# noinspection PyUnresolvedReferences
from Queue import Empty
from ...core.exceptions import LDAPExtensionError
from ...protocol.persistentSearch import persistent_search_control
from ... import SEQUENCE_TYPES
from ...utils.dn import safe_dn
class PersistentSearch(object):
def __init__(self,
connection,
search_base,
search_filter,
search_scope,
dereference_aliases,
attributes,
size_limit,
time_limit,
controls,
changes_only,
events_type,
notifications,
streaming,
callback
):
if connection.strategy.sync:
raise LDAPExtensionError('Persistent Search needs an asynchronous streaming connection')
if connection.check_names and search_base:
search_base = safe_dn(search_base)
self.connection = connection
self.changes_only = changes_only
self.notifications = notifications
self.message_id = None
self.base = search_base
self.filter = search_filter
self.scope = search_scope
self.dereference_aliases = dereference_aliases
self.attributes = attributes
self.size_limit = size_limit
self.time_limit = time_limit
self.connection.strategy.streaming = streaming
if callback and callable(callback):
self.connection.strategy.callback = callback
elif callback:
raise LDAPExtensionError('callback is not callable')
if not isinstance(controls, SEQUENCE_TYPES):
self.controls = []
else:
self.controls = controls
if events_type and changes_only and notifications:
self.controls.append(persistent_search_control(events_type, changes_only, notifications))
self.start()
def start(self):
if self.message_id: # persistent search already started
return
if not self.connection.bound:
self.connection.bind()
with self.connection.strategy.async_lock:
self.message_id = self.connection.search(search_base=self.base,
search_filter=self.filter,
search_scope=self.scope,
dereference_aliases=self.dereference_aliases,
attributes=self.attributes,
size_limit=self.size_limit,
time_limit=self.time_limit,
controls=self.controls)
self.connection.strategy.persistent_search_message_id = self.message_id
def stop(self, unbind=True):
self.connection.abandon(self.message_id)
if unbind:
self.connection.unbind()
if self.message_id in self.connection.strategy._responses:
del self.connection.strategy._responses[self.message_id]
if hasattr(self.connection.strategy, '_requests') and self.message_id in self.connection.strategy._requests: # asynchronous strategy has a dict of request that could be returned by get_response()
del self.connection.strategy._requests[self.message_id]
self.connection.strategy.persistent_search_message_id = None
self.message_id = None
def next(self, block=False, timeout=None):
if not self.connection.strategy.streaming and not self.connection.strategy.callback:
try:
return self.connection.strategy.events.get(block, timeout)
except Empty:
return None
raise LDAPExtensionError('Persistent search is not accumulating events in queue')
def funnel(self, block=False, timeout=None):
done = False
while not done:
try:
entry = self.connection.strategy.events.get(block, timeout)
except Empty:
yield None
if entry['type'] == 'searchResEntry':
yield entry
else:
done = True
yield entry

View File

@@ -0,0 +1,72 @@
"""
"""
# Created on 2014.04.30
#
# 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 HASHED_NONE
from ...extend.operation import ExtendedOperation
from ...protocol.rfc3062 import PasswdModifyRequestValue, PasswdModifyResponseValue
from ...utils.hashed import hashed
from ...protocol.sasl.sasl import validate_simple_password
from ...utils.dn import safe_dn
from ...core.results import RESULT_SUCCESS
# implements RFC3062
class ModifyPassword(ExtendedOperation):
def config(self):
self.request_name = '1.3.6.1.4.1.4203.1.11.1'
self.request_value = PasswdModifyRequestValue()
self.asn1_spec = PasswdModifyResponseValue()
self.response_attribute = 'new_password'
def __init__(self, connection, user=None, old_password=None, new_password=None, hash_algorithm=None, salt=None, controls=None):
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
if user:
if connection.check_names:
user = safe_dn(user)
self.request_value['userIdentity'] = user
if old_password:
if not isinstance(old_password, bytes): # bytes are returned raw, as per RFC (4.2)
old_password = validate_simple_password(old_password, True)
self.request_value['oldPasswd'] = old_password
if new_password:
if not isinstance(new_password, bytes): # bytes are returned raw, as per RFC (4.2)
new_password = validate_simple_password(new_password, True)
if hash_algorithm is None or hash_algorithm == HASHED_NONE:
self.request_value['newPasswd'] = new_password
else:
self.request_value['newPasswd'] = hashed(hash_algorithm, new_password, salt)
def populate_result(self):
try:
self.result[self.response_attribute] = str(self.decoded_response['genPasswd'])
except TypeError: # optional field can be absent, so returns True if operation is successful else False
if self.result['result'] == RESULT_SUCCESS:
self.result[self.response_attribute] = True
else: # change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
self.result[self.response_attribute] = False
if self.connection.raise_exceptions:
from ...core.exceptions import LDAPOperationResult
raise LDAPOperationResult(result=self.result['result'], description=self.result['description'], dn=self.result['dn'], message=self.result['message'], response_type=self.result['type'])

View File

@@ -0,0 +1,40 @@
"""
"""
# Created on 2014.04.30
#
# 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/>.
# implements RFC4532
from ...extend.operation import ExtendedOperation
from ...utils.conv import to_unicode
class WhoAmI(ExtendedOperation):
def config(self):
self.request_name = '1.3.6.1.4.1.4203.1.11.3'
self.response_attribute = 'authzid'
def populate_result(self):
try:
self.result['authzid'] = to_unicode(self.decoded_response) if self.decoded_response else None
except TypeError:
self.result['authzid'] = self.decoded_response if self.decoded_response else None