# Copyright (C) 2009-2019 Martin Slouf <martinslouf@users.sourceforge.net>
#
# This file is a part of Summer.
#
# Summer 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Module ``lsf`` defines :py:class:`LdapSessionFactory` class which is
central point for your *LDAP database* access.
Analogy to :py:mod:`summer.sf`.
"""
import ldap3
import logging
import threading
logger = logging.getLogger(__name__)
[docs]class LdapConnectionProvider(object):
"""Class to be used as based for providing LDAP session.
See default implementation in
:py:class:`summer.lsf.DefaultLdapConnectionProvider`.
"""
[docs] def __init__(self, base: str):
self._server = None
self._base = base
self._connection = None
@property
def server(self) -> ldap3.Server:
"""LDAP server property.
Returns:
ldap3.Server: LDAP server instance
"""
return self._server
@property
def base(self) -> str:
"""LDAP base property -- convenience value defining portion of a LDAP
subtree of interest.
Returns:
str: Optional property, can be left empty; LDAP base DN for a
subtree of interest to be used by queries
"""
return self._base
[docs] def create_connection(self) -> ldap3.Connection:
"""Each call produces a new connection.
Returns:
ldap3.Connection: new instance of LDAP connection
"""
pass
[docs]class DefaultLdapConnectionProvider(LdapConnectionProvider):
lock = threading.RLock()
[docs] def __init__(self, host: str, port: int, login: str, password: str, base: str):
"""
Args:
host (str): LDAP base dn
port (int): server port
login (str): server login
password (str): server password in clear text
base (str): see :py:meth:`LdapConnectionProvider.base`
"""
LdapConnectionProvider.__init__(self, base)
self._host = host
self._port = port
self._login = login
self._password = password
self._base = base
self._server = None
@property
def host(self) -> str:
return self._host
@property
def port(self) -> int:
return self._port
@property
def login(self) -> str:
return self._login
@property
def password(self) -> str:
return self._password
@property
def server(self) -> ldap3.Server:
if not self._server:
with DefaultLdapConnectionProvider.lock:
if not self._connection:
self._server = ldap3.Server(self.host, self.port, get_info=ldap3.ALL)
return self._server
[docs] def create_connection(self) -> ldap3.Connection:
return ldap3.Connection(
self.server,
user=self.login,
password=self.password,
authentication=ldap3.SIMPLE,
client_strategy=ldap3.SYNC,
check_names=True)
[docs]class LdapSessionFactory(object):
"""Thread safe *ldap3* session provider. Analogy to
:py:class:`summer.sf.SessionFactory`.
"""
[docs] class Local(threading.local):
"""Thread local session wrapper.
There is an active :py:class:`ldap3.Connection` instance in
:py:attr:`ldap_session` attribute.
"""
[docs] def __init__(self, ldap_connection_provider: LdapConnectionProvider):
threading.local.__init__(self)
self._ldap_connection_provider = ldap_connection_provider
self._connection = None
self._active = False
def __del__(self):
self.unbind()
@property
def base(self) -> str:
"""Delegates to :py:meth:`LdapConnectionProvider.base`."""
return self._ldap_connection_provider.base
@property
def connection(self) -> ldap3.Connection:
if self._connection:
logger.debug("accessing connection = %s", self._connection)
else:
self._connection = self._ldap_connection_provider.create_connection()
logger.debug("new thread local connection created, session = %s", self._connection)
return self._connection
@property
def active(self) -> bool:
"""Get status of current ldap session.
Returns:
bool: `True` if session is bound, `False` otherwise.
"""
return self._active
def bind(self):
self.connection.bind()
self._active = True
def unbind(self):
# NOTE martin 2016-09-17 -- direct access to attributes, not through properties
if self._connection:
self._connection.unbind()
self._connection = None
self._active = False
[docs] def __init__(self, ldap_connection_provider: LdapConnectionProvider):
"""Creates :py:class:`LdapSessionFactory` instance.
Args:
ldap_connection_provider (LdapConnectionProvider): LDAP connection provider
base (str): LDAP base dn
"""
self._ldap_connection_provider = ldap_connection_provider
self._session = LdapSessionFactory.Local(ldap_connection_provider)
@property
def base(self) -> str:
"""Delegates to :py:meth:`LdapSessionFactory.Local.base`"""
return self.session.base
@property
def session(self):
"""Get current thread-local *ldap3 session* wrapper (creating one, if
non-existent).
Returns:
LdapSessionFactory.Local: existing or just created
*ldap3 session* wrapper
"""
return self._session
@property
def connection(self) -> ldap3.Connection:
"""Delegates to :py:meth:`LdapSessionFactory.Local.connection`"""
return self.session.connection