Skip to content

Commit

Permalink
v 0.9.9.3
Browse files Browse the repository at this point in the history
  • Loading branch information
cannatag committed Nov 15, 2015
1 parent b43e4ea commit 2aee189
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 59 deletions.
10 changes: 7 additions & 3 deletions CHANGES.txt
@@ -1,12 +1,16 @@
# changes file for ldap3
# generated on 2015-11-11 19:35:51.384492
# generated on 2015-11-15 23:24:19.725251
# version 0.9.9.3

* 0.9.9.3 2015.11.08
* 0.9.9.3 2015.11.15
- Added LDAPI (LDAP over IPC) support for unix socket communication
- Added mandatory_in and optional_in in server schema for attribute types. Now you can see in which classes attributes are used
- Added last_transmitted_time and last_received_time to Usage object to track time of the last sent and received operation
- Exception SessionTerminatedByServer renamed to SessionTerminatedByServerError and added to ldap3 namespace
- added get_config_parameter() in ldap3 to read the current value of parameters modified in ldap3 namespace
- Added get_config_parameter() in ldap3 to read the current value of parameters modified in the ldap3 namespace
- Added SASL mechanism name as constants in the ldap3 namespace
- Added escape_filter_chars in utils.conv (thanks Peter)
- reverted ALL_ATTRIBUTES behaviour in search to 0.9.9.1 (thanks Petros)

* 0.9.9.2 2015.10.19
- Fixed hasattr() behaviour for Entry object in Python 3
Expand Down
9 changes: 6 additions & 3 deletions _changelog.txt
@@ -1,9 +1,12 @@
* 0.9.9.3 2015.11.08
* 0.9.9.3 2015.11.15
- Added LDAPI (LDAP over IPC) support for unix socket communication
- Added mandatory_in and optional_in in server schema for attribute types. Now you can see in which classes attributes are used
- Added last_transmitted_time and last_received_time to Usage object to track time of the last sent and received operation
- Exception SessionTerminatedByServer renamed to SessionTerminatedByServerError and added to ldap3 namespace
- added get_config_parameter() in ldap3 to read the current value of parameters modified in ldap3 namespace
- added SASL mechanism name as constants in init
- Added get_config_parameter() in ldap3 to read the current value of parameters modified in the ldap3 namespace
- Added SASL mechanism name as constants in the ldap3 namespace
- Added escape_filter_chars in utils.conv (thanks Peter)
- reverted ALL_ATTRIBUTES behaviour in search to 0.9.9.1 (thanks Petros)

* 0.9.9.2 2015.10.19
- Fixed hasattr() behaviour for Entry object in Python 3
Expand Down
22 changes: 21 additions & 1 deletion docs/manual/source/bind.rst
Expand Up @@ -205,7 +205,7 @@ By default the library attempts to bind against the service principal for the do
NTLM
----

The ldap3 library support an additional method to bind to Active Directory servers via the NTLM method::
The ldap3 library supports an additional method to bind to Active Directory servers via the NTLM method::

# import class and constants
from ldap3 import Server, Connection, SIMPLE, SYNC, ALL, SASL, NTLM)
Expand All @@ -221,6 +221,26 @@ This authentication method is specific for Active Directory and uses a proprieta
that breaks the LDAP RFC but can be used to access AD.


LDAPI (LDAP over IPC)
---------------------

If your LDAP server provides a UNIX socket connection (*Interprocess Communication*) you can use the **ldapi:** schema to access it from the
same machine::

>>> # accessing OpenLDAP server in a root user session
>>> s = Server('ldapi:///var/run/slapd/ldapi')
>>> c = Connection(s, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='')
>>> c.bind()
>>> True
>>> c.extend.standard.who_am_i()
>>> dn:cn=config

Using the SASL *EXTERNAL* mechanism allows you to provide to the server the credentials of the logged user.

While accessing your LDAP server via a UNIX socket you can perform any usual LDAP operation. This should be faster than using a TCP connection.
You don't need to use SSL when connecting via a socket because all the communication is in the server memory and is not exposed on the wire.


Extended logging
----------------
To get an idea of what's happening when you perform a Simple Bind operation using the StartTLS security feature this is
Expand Down
6 changes: 6 additions & 0 deletions docs/manual/source/features.rst
Expand Up @@ -71,3 +71,9 @@ ldap3 Features
6. Simplified query construction language:

* The library includes an optional **abstraction layer** for performing LDAP queries.

7. Clear or secured access

* ldap3 allows plaintext (**ldap:**), secure (**ldaps:**) and UNIX socket (**ldapi:**) access to the LDAP server.

* The NTLM access method is available to connect to Active Directory servers
9 changes: 9 additions & 0 deletions ldap3/core/connection.py
Expand Up @@ -77,6 +77,11 @@ def _format_socket_endpoint(endpoint):
elif endpoint and len(endpoint) == 4: # IPv6
return '[' + str(endpoint[0]) + ']:' + str(endpoint[1])

try:
return str(endpoint)
except Exception:
return '?'


def _format_socket_endpoints(sock):
if sock:
Expand Down Expand Up @@ -595,8 +600,12 @@ def search(self,
if isinstance(paged_size, int):
if log_enabled(PROTOCOL):
log(PROTOCOL, 'performing paged search for %d items with cookie <%s> for <%s>', paged_size, escape_bytes(paged_cookie), self)
# real_search_control_value = RealSearchControlValue()
# real_search_control_value['size'] = Size(paged_size)
# real_search_control_value['cookie'] = Cookie(paged_cookie) if paged_cookie else Cookie('')
if controls is None:
controls = []
# controls.append(('1.2.840.113556.1.4.319', paged_criticality if isinstance(paged_criticality, bool) else False, encoder.encode(real_search_control_value)))
controls.append(paged_search_control(paged_criticality, paged_size, paged_cookie))

request = search_operation(search_base, search_filter, search_scope, dereference_aliases, attributes, size_limit, time_limit, types_only, self.server.schema if self.server else None)
Expand Down
36 changes: 21 additions & 15 deletions ldap3/core/server.py
Expand Up @@ -36,13 +36,17 @@
from .tls import Tls
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL

try:
from urllib.parse import unquote
except ImportError:
from urllib import unquote

try:
from socket import AF_UNIX
unix_socket_available = True
except ImportError:
unix_socket_available = False


class Server(object):
"""
LDAP Server definition class
Expand Down Expand Up @@ -74,7 +78,7 @@ def __init__(self,
connect_timeout=None,
mode=IP_V6_PREFERRED):

self.is_unix_socket = False
self.ipc = False
url_given = False
if host.lower().startswith('ldap://'):
self.host = host[7:]
Expand All @@ -85,17 +89,19 @@ def __init__(self,
use_ssl = True
url_given = True
elif host.lower().startswith('ldapi://') and unix_socket_available:
self.host = host[8:]
self.is_unix_socket = True
self.ipc = True
use_ssl = False
url_given = True
elif host.lower().startswith('ldapi://') and not unix_socket_available:
raise LDAPSocketOpenError('LDAP over IPC not available')
else:
self.host = host

if self.is_unix_socket:
self.host = None
if self.ipc:
if str == bytes: # Python 2
self.host = unquote(host[7:]).decode('utf-8')
else:
self.host = unquote(host[7:], encoding='utf-8')
self.port = None
elif ':' in self.host and self.host.count(':') == 1:
hostname, _, hostport = self.host.partition(':')
Expand Down Expand Up @@ -130,7 +136,7 @@ def __init__(self,
log(ERROR, 'invalid server address for <%s>', self.host)
raise LDAPInvalidServerError()

if not self.is_unix_socket:
if not self.ipc:
self.host.rstrip('/')
if not use_ssl and not port:
port = 389
Expand Down Expand Up @@ -169,13 +175,13 @@ def __init__(self,

self.tls = Tls() if self.ssl and not tls else tls

if not self.is_unix_socket:
if not self.ipc:
if self._is_ipv6(self.host):
self.name = ('ldaps' if self.ssl else 'ldap') + '://[' + self.host + ']:' + str(self.port)
else:
self.name = ('ldaps' if self.ssl else 'ldap') + '://' + self.host + ':' + str(self.port)
else:
self.name = self.host
self.name = host

self.get_info = get_info
self._dsa_info = None
Expand All @@ -201,7 +207,7 @@ def _is_ipv6(host):

def __str__(self):
if self.host:
s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.is_unix_socket else '')
s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.ipc else '')
else:
s = object.__str__(self)
return s
Expand All @@ -221,8 +227,8 @@ def address_info(self):
# converts addresses tuple to list and adds a 6th parameter for availability (None = not checked, True = available, False=not available) and a 7th parameter for the checking time
addresses = None
try:
if self.is_unix_socket:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNIX, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
if self.ipc:
addresses = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, self.host, None)]
else:
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
except (socket.gaierror, AttributeError):
Expand Down Expand Up @@ -487,10 +493,10 @@ def from_definition(host, dsa_info, dsa_schema, port=None, use_ssl=False, format
return dummy

def candidate_addresses(self):
if self.is_unix_socket:
candidates = [socket.AF_UNIX, socket.SOCK_STREAM, socket.IPPROTO_TCP, self.host, None, None]
if self.ipc:
candidates = self.address_info
if log_enabled(BASIC):
log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.host)
log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.name)
else:
# selects server address based on server mode and availability (in address[5])
addresses = self.address_info[:] # copy to avoid refreshing while searching candidates
Expand Down
65 changes: 31 additions & 34 deletions ldap3/strategy/base.py
Expand Up @@ -117,7 +117,7 @@ def open(self, reset_usage=True, read_server_info=True):
try:
if log_enabled(BASIC):
log(BASIC, 'try to open candidate address %s', candidate_address[:-2])
self._open_socket(candidate_address, self.connection.server.ssl, unix_socket=self.connection.server.is_unix_socket)
self._open_socket(candidate_address, self.connection.server.ssl, unix_socket=self.connection.server.ipc)
self.connection.server.current_address = candidate_address
self.connection.server.update_availability(candidate_address, True)
break
Expand Down Expand Up @@ -181,50 +181,47 @@ def _open_socket(self, address, use_ssl=False, unix_socket=False):
raise LDAPExceptionError if unable to open or connect socket
"""
exc = None
if unix_socket:
raise NotImplementedError ('ldapi still not imolemented')
else:
try:
self.connection.socket = socket.socket(*address[:3])
except Exception as e:
self.connection.last_error = 'socket creation error: ' + str(e)
exc = e
try:
self.connection.socket = socket.socket(*address[:3])
except Exception as e:
self.connection.last_error = 'socket creation error: ' + str(e)
exc = e

if exc:
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
if exc:
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)

raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)
raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)

try:
if self.connection.server.connect_timeout:
self.connection.socket.settimeout(self.connection.server.connect_timeout)
self.connection.socket.connect(address[4])
if self.connection.server.connect_timeout:
self.connection.socket.settimeout(None) # disable socket timeout - socket is in blocking mode
except socket.error as e:
self.connection.last_error = 'socket connection error: ' + str(e)
exc = e

if exc:
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)

if use_ssl:
try:
if self.connection.server.connect_timeout:
self.connection.socket.settimeout(self.connection.server.connect_timeout)
self.connection.socket.connect(address[4])
if self.connection.server.connect_timeout:
self.connection.socket.settimeout(None) # disable socket timeout - socket is in blocking mode
except socket.error as e:
self.connection.last_error = 'socket connection error: ' + str(e)
self.connection.server.tls.wrap_socket(self.connection, do_handshake=True)
if self.connection.usage:
self.connection._usage.wrapped_sockets += 1
except Exception as e:
self.connection.last_error = 'socket ssl wrapping error: ' + str(e)
exc = e

if exc:
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)

if use_ssl:
try:
self.connection.server.tls.wrap_socket(self.connection, do_handshake=True)
if self.connection.usage:
self.connection._usage.wrapped_sockets += 1
except Exception as e:
self.connection.last_error = 'socket ssl wrapping error: ' + str(e)
exc = e

if exc:
if log_enabled(ERROR):
log(ERROR, '<%s> for <%s>', self.connection.last_error, self.connection)
raise communication_exception_factory(LDAPSocketOpenError, exc)(self.connection.last_error)

if self.connection.usage:
self.connection._usage.open_sockets += 1

Expand Down
4 changes: 2 additions & 2 deletions ldap3/version.py
@@ -1,6 +1,6 @@
# THIS FILE IS AUTO-GENERATED. PLEASE DO NOT MODIFY# version file for ldap3
# generated on 2015-11-11 19:35:51.384492
# on system uname_result(system='Windows', node='GCW89227', release='post2012Server', version='6.3.9600', machine='AMD64', processor='Intel64 Family 6 Model 45 Stepping 7, GenuineIntel')
# generated on 2015-11-15 23:24:19.718253
# on system uname_result(system='Windows', node='GCNBHPW8', release='post2012Server', version='6.3.9600', machine='AMD64', processor='Intel64 Family 6 Model 58 Stepping 9, GenuineIntel')
# with Python 3.5.0 - ('v3.5.0:374f501f4567', 'Sep 13 2015 02:27:37') - MSC v.1900 64 bit (AMD64)
#
__version__ = '0.9.9.3'
Expand Down
11 changes: 10 additions & 1 deletion test/testBindOperation.py
Expand Up @@ -75,6 +75,15 @@ def test_ntlm(self):
def test_ldapi(self):
if test_server_type == 'SLAPD':
server = Server('ldapi:///var/run/slapd/ldapi')
connection = Connection(server, user=test_user, password=test_password, authentication=SASL, sasl_mechanism=EXTERNAL)
connection = Connection(server, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='')
connection.open()
connection.bind()
self.assertTrue(connection.bound)

def test_ldapi_encoded_url(self):
if test_server_type == 'SLAPD':
server = Server('ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi')
connection = Connection(server, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='')
connection.open()
connection.bind()
self.assertTrue(connection.bound)

0 comments on commit 2aee189

Please sign in to comment.