from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE
from ldap3.core.exceptions import LDAPBindError
from ldap3.core.exceptions import LDAPException
+from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
from ldap3.core.exceptions import LDAPSocketOpenError
from ldap3.core.exceptions import LDAPSocketReceiveError
from ..config.ldap import LdapConfiguration, LdapConnectionInfo
from ..xlate import XLATOR, format_list
-__version__ = '0.12.3'
+__version__ = '1.0.0'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
pass
+# =============================================================================
+class LdapSessionError(FatalLDAPError):
+ """Error in session handling with LDAP."""
+
+ pass
+
+
# =============================================================================
class PasswordFileOptionAction(argparse.Action):
"""Argparse action for a password file."""
apply_default_ldap_instance_if_not_given = True
default_default_ldap_instance = 'default'
default_wait_on_read_error = 1
+ default_single_session = False
# pattern_re_ldap_dn = (
# '^([a-z][a-z0-9-]*)=(?![ #])(((?![\\="+,;<>]).)|(\\[ \\#="+,;<>])|(\\[a-f0-9][a-f0-9]))*'
self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None,
cfg_class=LdapConfiguration, initialized=False, usage=None, description=None,
argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None,
- config_dir=DEFAULT_CONFIG_DIR, wait_on_read_error=None):
+ config_dir=DEFAULT_CONFIG_DIR, wait_on_read_error=None,
+ single_session=None):
"""Contrict the application object."""
self._password_file = None
self.ldap_instances = []
self.ldap_server = {}
self.ldap_connection = {}
self._wait_on_read_error = self.default_wait_on_read_error
+ self._single_session = self.default_single_session
super(BaseLdapApplication, self).__init__(
appname=appname, verbose=verbose, version=version, base_dir=base_dir,
if wait_on_read_error:
self.wait_on_read_error = wait_on_read_error
+ if single_session is not None:
+ self.single_session = single_session
+
# -----------------------------------------------------------
@property
def password_file(self):
raise ValueError(msg)
self._wait_on_read_error = val
+ # -----------------------------------------------------------
+ @property
+ def single_session(self):
+ """
+ Execute all LDAP operations in one single TCP session.
+
+ Otherwise each read or write operation will be executed
+ in a separate TCP session.
+ """
+ return self._single_session
+
+ @single_session.setter
+ def single_session(self, value):
+ if value is None:
+ self._single_session = self.default_single_session
+ return
+ self._single_session = to_bool(value)
+
# -------------------------------------------------------------------------
def as_dict(self, short=True):
"""
res['default_default_ldap_instance'] = self.default_default_ldap_instance
res['pattern_re_ldap_dn'] = self.pattern_re_ldap_dn
res['password_file'] = self.password_file
+ res['single_session'] = self.single_session
res['show_cmdline_ldap_timeout'] = self.show_cmdline_ldap_timeout
res['use_default_ldap_connection'] = self.use_default_ldap_connection
res['use_multiple_ldap_connections'] = self.use_multiple_ldap_connections
LOG.debug(_('Preparations ...'))
super(BaseLdapApplication, self).pre_run()
- LOG.debug(_('Open all necessary LDAP connections ...'))
+ if self.single_session:
+ LOG.debug(_('Open all necessary LDAP connections ...'))
- for inst in self.ldap_instances:
- self.connect_instance(inst)
+ for inst in self.ldap_instances:
+ self.connect_instance(inst, single=True)
# -------------------------------------------------------------------------
- def connect_instance(self, inst):
+ def connect_instance(self, inst, single=False):
"""Connect to the given LDAP instance."""
connect_info = self.cfg.ldap_connection[inst]
- ldap_server = self.get_ldap_server_obj(inst)
+ ldap_server = self.get_ldap_server_obj(inst, single=single)
self.ldap_server[inst] = ldap_server
if not connect_info.bind_pw:
inst=self.colored(connect_info.url, 'CYAN')) + ' '
connect_info.bind_pw = self.get_password(first_prompt, may_empty=False, repeat=False)
- ldap_connection = self.connect_to_ldap_server(ldap_server, inst)
+ ldap_connection = self.connect_to_ldap_server(ldap_server, inst, single=single)
self.ldap_connection[inst] = ldap_connection
- if self.verbose > 2:
+ min_verb_level = 3
+ if single:
+ min_verb_level = 2
+ if self.verbose > min_verb_level:
msg = _('Info about LDAP server {}:').format(connect_info.url)
msg += ' ' + repr(ldap_connection)
LOG.debug(msg)
# -------------------------------------------------------------------------
- def get_ldap_server_obj(self, inst):
+ def get_ldap_server_obj(self, inst, single=False):
"""Return the ldap3-Server object for the given instance."""
connect_info = self.cfg.ldap_connection[inst]
- if self.verbose > 2:
+ min_verb_level = 3
+ if single:
+ min_verb_level = 2
+ if self.verbose > min_verb_level:
msg = _('Trying to get LDAP server object for {} ...').format(connect_info.url)
LOG.debug(msg)
server_opts['get_info'] = DSA
server_opts['mode'] = IP_V4_PREFERRED
server_opts['connect_timeout'] = self.cfg.ldap_timeout
- if self.verbose > 2:
+ if self.verbose > min_verb_level:
msg = _('Connect options to server {!r}:').format(connect_info.url)
msg += ' ' + pp(server_opts)
LOG.debug(msg)
ldap_server = Server(connect_info.host, **server_opts)
- if self.verbose > 2:
+ if self.verbose > min_verb_level:
LOG.debug(_('LDAP server {s}: {re}').format(s=ldap_server, re=repr(ldap_server)))
return ldap_server
# -------------------------------------------------------------------------
- def connect_to_ldap_server(self, ldap_server, inst, bind_dn=None, bind_pw=None):
+ def connect_to_ldap_server(self, ldap_server, inst, bind_dn=None, bind_pw=None, single=False):
"""Connect to the given LDAP server."""
connect_info = self.cfg.ldap_connection[inst]
if not bind_dn:
if not bind_pw:
bind_pw = connect_info.bind_pw
- if self.verbose > 1:
+ min_verb_level = 2
+ if single:
+ min_verb_level = 1
+
+ if self.verbose > min_verb_level:
msg = _('Connecting to LDAP server {url} as {dn!r} ...').format(
url=connect_info.url, dn=bind_dn)
LOG.debug(msg)
LOG.debug(_('Disconnecting from all remaining LDAP instances ...'))
for inst in self.ldap_instances:
- self.disconnect_instance(inst)
+ self.disconnect_instance(inst, single=True)
# -------------------------------------------------------------------------
- def disconnect_instance(self, inst):
+ def disconnect_instance(self, inst, single=False):
"""Disconnect from the given instance."""
connect_info = self.cfg.ldap_connection[inst]
+ min_verb_level = 2
+ if single:
+ min_verb_level = 1
+
if inst in self.ldap_connection:
ldap_connection = self.ldap_connection[inst]
- if self.verbose > 1:
+ if self.verbose > min_verb_level:
LOG.debug(_('Unbinding from LDAP server {!r} ...').format(connect_info.url))
ldap_connection.unbind()
ldap_connection = None
del self.ldap_connection[inst]
- if inst in self.ldap_server:
- if self.verbose > 1:
- LOG.debug(_('Disconnecting from LDAP server {!r} ...').format(connect_info.url))
+ if inst in self.ldap_server and single:
+ if self.verbose > min_verb_level:
+ LOG.debug(_('Removing LDAP server connection data {!r} ...').format(
+ connect_info.url))
del self.ldap_server[inst]
# -------------------------------------------------------------------------
with the resulting attributes as values.
"""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
+
if scope is None:
scope = SUBTREE
msg += ' ' + format_list(attributes, do_repr=True)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=scope, attributes=attributes,
- search_filter=ldap_filter, time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=scope, attributes=attributes,
+ search_filter=ldap_filter, time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 4:
self, inst, ldap_filter=None, base_dn=None, scope=None, no_complain=False):
"""Get DNs of all entries in the given LDAP instance and sort them."""
connect_info = self.cfg.ldap_connection[inst]
+
if not base_dn:
base_dn = connect_info.base_dn
- ldap = self.ldap_connection[inst]
if scope is None:
scope = SUBTREE
if self.verbose > 1:
LOG.debug(_('Using LDAP filter: {!r}').format(ldap_filter))
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 2:
"""Get Object classes and DNs of all entries in the given LDAP instance."""
connect_info = self.cfg.ldap_connection[inst]
base_dn = connect_info.base_dn
- ldap = self.ldap_connection[inst]
result = CIDict()
attributes = ['objectClass']
if self.verbose > 1:
LOG.debug(_('Using LDAP filter: {!r}').format(ldap_filter))
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 5:
"""Get the DN of the user with the given mail address in the given LDAP instance."""
connect_info = self.cfg.ldap_connection[inst]
base_dn = connect_info.base_dn
- ldap = self.ldap_connection[inst]
result = []
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 4:
"""Get the DN of the user with the given uid (POSIX name) in the given LDAP instance."""
connect_info = self.cfg.ldap_connection[inst]
base_dn = connect_info.base_dn
- ldap = self.ldap_connection[inst]
result = []
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 4:
"""Get the DN of the user with the given cn (common name) in the given LDAP instance."""
connect_info = self.cfg.ldap_connection[inst]
base_dn = connect_info.base_dn
- ldap = self.ldap_connection[inst]
result = []
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
if self.verbose > 4:
def get_entry(self, dn, inst, attributes=None, operational_attributes=False, tries=3):
"""Get an complete LDAP entry by the given DN in the given instance."""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
if attributes is None:
attributes = [ALL_ATTRIBUTES]
cur_try = 0
e_msg = None
while cur_try < tries:
+
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
e_msg = None
cur_try += 1
try:
search_base=dn, search_scope=BASE, attributes=attributes,
get_operational_attributes=operational_attributes, search_filter=sfilter,
time_limit=self.cfg.ldap_timeout)
+
except (LDAPSocketReceiveError, LDAPSocketOpenError) as e:
e_msg = str(e)
if cur_try >= tries:
LOG.debug(_('Waiting because of a failing read operation.'))
time.sleep(self.wait_on_read_error)
+ except LDAPSessionTerminatedByServerError as e:
+ msg = _('Session terminated on reading entry {dn!r} from instance {i!r}:').format(
+ dn=dn, i=inst) + ' ' + str(e)
+ raise LdapSessionError(msg)
+
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
+
if e_msg:
msg = _('Error on reading entry {dn!r} from instance {inst!r}:').format(
dn=dn, inst=inst) + ' ' + e_msg
def add_entry(self, inst, dn, object_classes, target_entry, ldap=None):
"""Create a LDAP entry."""
connect_info = self.cfg.ldap_connection[inst]
- if not ldap:
- ldap = self.ldap_connection[inst]
+ keep_ldap = False
if self.verbose > 2:
msg = _('Creating entry {dn!r} on {uri}:').format(
LOG.info(_('Simulation mode - entry will not be created.'))
return True
+ if ldap:
+ keep_ldap = True
+ else:
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
try:
req_status, req_result, req_response, req_whatever = ldap.add(
dn, object_class=object_classes, attributes=target_entry)
msg += '\nobjectClasses:\n' + pp(object_classes)
msg += '\nAttributes:\n' + pp(target_entry)
raise WriteLDAPItemError(msg)
+ finally:
+ if not self.single_session or not keep_ldap:
+ self.disconnect_instance(inst)
# Example result on a not successful modification:
# { 'description': 'objectClassViolation',
def modify_entry(self, inst, dn, changes, ldap=None):
"""Mofify an existing LDAP entry."""
connect_info = self.cfg.ldap_connection[inst]
- if not ldap:
- ldap = self.ldap_connection[inst]
+ keep_ldap = False
if self.verbose > 1:
msg = _('Applying changes on {uri} to DN {dn!r}:').format(
LOG.info(_('Simulation mode - changes are not applied.'))
return True
+ if ldap:
+ keep_ldap = True
+ else:
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
try:
req_status, req_result, req_response, req_whatever = ldap.modify(dn, changes)
except LDAPException as e:
dn=dn, c=e.__class__.__name__, e=e)
msg += '\n' + _('Changes:') + '\n' + pp(changes)
raise WriteLDAPItemError(msg)
+ finally:
+ if not self.single_session or not keep_ldap:
+ self.disconnect_instance(inst)
# Example result on a not successful modification:
# { 'description': 'objectClassViolation',
def delete_entry(self, inst, dn, ldap=None):
"""Delete a LDAP entry."""
connect_info = self.cfg.ldap_connection[inst]
- if not ldap:
- ldap = self.ldap_connection[inst]
+ keep_ldap = False
msg = _('Deleting LDAP entry {dn!r} on {uri} ...').format(
uri=connect_info.url, dn=dn)
LOG.info(_('Simulation mode - deletion will not be executed.'))
return True
+ if ldap:
+ keep_ldap = True
+ else:
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
try:
req_status, req_result, req_response, req_whatever = ldap.delete(dn)
except LDAPException as e:
msg = _('Deletion of {dn!r} was NOT successfull - {c}: {e}').format(
c=e.__class__.__name__, e=e)
raise DeleteLDAPItemError(msg)
+ finally:
+ if not self.single_session or not keep_ldap:
+ self.disconnect_instance(inst)
if self.verbose > 1:
LOG.debug(_('Deletion status: {!r}.').format(req_status))
def get_group_memberships(self, inst, dn, base_dn=None):
"""Return a list with DNs of all groups containing the given DN as a member."""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
if not base_dn:
base_dn = connect_info.base_dn
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
for entry in req_response:
def get_unique_group_memberships(self, inst, dn, base_dn=None):
"""Return a list with DNs of all groups containing the given DN as a unique member."""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
if not base_dn:
base_dn = connect_info.base_dn
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
for entry in req_response:
def get_posix_group_memberships(self, inst, uid, base_dn=None):
"""Return a list with DNs of all POSIX groups containing the given UID as a member."""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
if not base_dn:
base_dn = connect_info.base_dn
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
for entry in req_response:
def get_sudo_group_memberships(self, inst, uid, base_dn=None):
"""Return a list with DNs of all sudo roles containing the given UID as a member."""
connect_info = self.cfg.ldap_connection[inst]
- ldap = self.ldap_connection[inst]
if not base_dn:
base_dn = connect_info.base_dn
uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
LOG.debug(msg)
- req_status, req_result, req_response, req_whatever = ldap.search(
- search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
- get_operational_attributes=False, attributes=attributes,
- time_limit=self.cfg.ldap_timeout)
+ if inst not in self.ldap_connection:
+ self.connect_instance(inst)
+ ldap = self.ldap_connection[inst]
+
+ try:
+ req_status, req_result, req_response, req_whatever = ldap.search(
+ search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+ get_operational_attributes=False, attributes=attributes,
+ time_limit=self.cfg.ldap_timeout)
+ finally:
+ if not self.single_session:
+ self.disconnect_instance(inst)
if req_status:
for entry in req_response: