Skip to content

Commit

Permalink
Merge pull request #23 from nickw444/nwhyte-better-ssl-opts
Browse files Browse the repository at this point in the history
Additional TLS options
  • Loading branch information
nickw444 authored Jul 3, 2017
2 parents fb9e4ba + aa04c9d commit f2a2b45
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 11 deletions.
10 changes: 9 additions & 1 deletion docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ Core
``LDAP_USE_SSL`` Specifies whether the default server
connection should use SSL. Defaults to
``False``.

``LDAP_ADD_SERVER`` Specifies whether the default server
as specified in ``LDAP_HOST`` should be
added to the server pool. Defaults to
``True``. Servers can be added via the
``add_server`` method.

``LDAP_READONLY`` Specified if connections made to the
server are readonly. Defaults to
``True``

``LDAP_BIND_DIRECT_CREDENTIALS`` Instead of searching for a DN of a user
you can instead bind directly to the
directory. Setting this ``True`` will
Expand All @@ -38,6 +46,7 @@ Core
up their user info. You will only know
if their credentials are correct or
not. Defaults to ``False``.

``LDAP_ALWAYS_SEARCH_BIND`` Specifies whether or not the library
should perform direct binds. When the
RDN attribute is the same as the login
Expand Down Expand Up @@ -86,7 +95,6 @@ Core
when binding to LDAP. Defaults to
``'AUTH_SIMPLE'``


======================================== =======================================


Expand Down
9 changes: 9 additions & 0 deletions docs/source/quick_start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ Basic Scripting Usage (Without a Flask App)
This is an example for if you wish to simply use the module, maybe for testing or for use in some other environment.

.. literalinclude:: ../../ldap_noapp.py


Custom TLS Context
~~~~~~~~~~~~~~~~~~

This is an example that shows how to initialize a custom TLS context for
securing communication between the module and a secure LDAP (LDAPS server.

.. literalinclude:: ../../ldap_noapp.py
25 changes: 18 additions & 7 deletions flask_ldap3_login/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,16 @@ def init_config(self, config):
self.config.setdefault('LDAP_GROUP_MEMBERS_ATTR', 'uniqueMember')
self.config.setdefault(
'LDAP_GET_GROUP_ATTRIBUTES', ldap3.ALL_ATTRIBUTES)
self.config.setdefault('LDAP_ADD_SERVER', True)

self.add_server(
hostname=self.config.get('LDAP_HOST'),
port=self.config.get('LDAP_PORT'),
use_ssl=self.config.get('LDAP_USE_SSL')
)
if self.config['LDAP_ADD_SERVER']:
self.add_server(
hostname=self.config['LDAP_HOST'],
port=self.config['LDAP_PORT'],
use_ssl=self.config['LDAP_USE_SSL']
)

def add_server(self, hostname, port, use_ssl):
def add_server(self, hostname, port, use_ssl, tls_ctx=None):
"""
Add an additional server to the server pool and return the
freshly created server.
Expand All @@ -150,11 +152,20 @@ def add_server(self, hostname, port, use_ssl):
hostname (str): Hostname of the server
port (int): Port of the server
use_ssl (bool): True if SSL is to be used when connecting.
tls_ctx (ldap3.Tls): An optional TLS context object to use
when connecting.
Returns:
ldap3.Server: The freshly created server object.
"""
server = ldap3.Server(hostname, port=port, use_ssl=use_ssl)
if not use_ssl and tls_ctx:
raise ValueError("Cannot specify a TLS context and not use SSL!")
server = ldap3.Server(
hostname,
port=port,
use_ssl=use_ssl,
tls=tls_ctx
)
self._server_pool.add(server)
return server

Expand Down
14 changes: 12 additions & 2 deletions flask_ldap3_login_tests/MockTypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ def lamb(data):


class Server(mock.MagicMock):
pass
def __init__(self, hostname, port=None, use_ssl=None, tls=None):
mock.MagicMock.__init__(self)
self.hostname = hostname
self.port = port
self.use_ssl = use_ssl
self.tls = tls


class Connection(mock.MagicMock):
Expand Down Expand Up @@ -152,4 +157,9 @@ def result(self):


class ServerPool(mock.MagicMock):
pass
def __init__(self, servers, *args, **kwargs):
mock.MagicMock.__init__(self)
self.servers = servers

def add(self, server):
self.servers.append(server)
140 changes: 139 additions & 1 deletion flask_ldap3_login_tests/test_ldap3_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import mock
from flask import abort
import logging
from ldap3 import Tls

from .Directory import DIRECTORY
from .MockTypes import Server, Connection, ServerPool
Expand Down Expand Up @@ -446,4 +447,141 @@ def test_server_pool(self):
"""
for i in range(10):
self.manager.init_app(self.app)
self.assertEquals(len(list(self.manager._server_pool)), 1)
self.assertEqual(len(list(self.manager._server_pool)), 1)


class LDAPAddServerConfigTestCase(unittest.TestCase):
"""Tests for the `LDAP_ADD_SERVER` config key"""

DEFAULT_CONFIG = dict(
LDAP_HOST='ad.mydomain.com',
LDAP_BASE_DN='dc=mydomain,dc=com',
LDAP_USER_DN='ou=users',
LDAP_GROUP_DN='ou=groups',
LDAP_BIND_USER_DN='cn=Bind,dc=mydomain,dc=com',
LDAP_BIND_USER_PASSWORD='bind123',
LDAP_USER_RDN_ATTR='cn',
LDAP_USER_LOGIN_ATTR='cn'
)

def test_server_added_when_unset(self):
"""
Ensures a default server is added when `LDAP_ADD_SERVER` is not set.
"""
config = dict(LDAPAddServerConfigTestCase.DEFAULT_CONFIG)

ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(config)

self.assertEqual(len(list(ldap3_manager._server_pool)), 1)

def test_server_added_when_true(self):
"""
Ensures a default server is added when `LDAP_ADD_SERVER` is True.
"""
config = dict(LDAPAddServerConfigTestCase.DEFAULT_CONFIG)
config['LDAP_ADD_SERVER'] = True

ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(config)

self.assertEqual(len(list(ldap3_manager._server_pool)), 1)

def test_server_added_when_false(self):
"""
Ensures no server is added when `LDAP_ADD_SERVER` is False.
"""
config = dict(LDAPAddServerConfigTestCase.DEFAULT_CONFIG)
config['LDAP_ADD_SERVER'] = False

ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(config)

self.assertEqual(len(list(ldap3_manager._server_pool)), 0)


class AddServerTestCase(unittest.TestCase):
"""
Tests for the `add_server` method.
"""

DEFAULT_CONFIG = dict(
LDAP_HOST='ad.mydomain.com',
LDAP_BASE_DN='dc=mydomain,dc=com',
LDAP_USER_DN='ou=users',
LDAP_GROUP_DN='ou=groups',
LDAP_BIND_USER_DN='cn=Bind,dc=mydomain,dc=com',
LDAP_BIND_USER_PASSWORD='bind123',
LDAP_USER_RDN_ATTR='cn',
LDAP_USER_LOGIN_ATTR='cn',
LDAP_ADD_SERVER=False
)

def test_error_on_use_ssl_and_tls_ctx(self):
"""
Ensures a ValueError is thrown when use_ssl is False and a TLS context
is passed together.
"""
ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(AddServerTestCase.DEFAULT_CONFIG)

def add_server():
return ldap3_manager.add_server("ad2.mydomain.com", 389,
use_ssl=False, tls_ctx=object())

self.assertRaises(ValueError, add_server)

@mock.patch('ldap3.Server', new=Server)
@mock.patch('ldap3.ServerPool', new=ServerPool)
def test_server_with_no_tls_ctx(self):
"""
Ensures a server is created/added to the pool, however that the server
was instantiated with `tls=None` and use_ssl=False
"""
ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(AddServerTestCase.DEFAULT_CONFIG)
ldap3_manager.add_server("ad2.mydomain.com", 389, use_ssl=False, tls_ctx=None)

self.assertEqual(len(ldap3_manager._server_pool.servers), 1)

server = ldap3_manager._server_pool.servers[-1]
self.assertEqual(server.tls, None)
self.assertFalse(server.use_ssl)

@mock.patch('ldap3.Server', new=Server)
@mock.patch('ldap3.ServerPool', new=ServerPool)
def test_server_with_no_tls_with_ssl(self):
"""
Ensures a server is created/added to the pool, however that the server
was instantiated with `tls=None` and use_ssl=True.
"""
ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(AddServerTestCase.DEFAULT_CONFIG)
ldap3_manager.add_server("ad2.mydomain.com", 389,
use_ssl=True, tls_ctx=None)

self.assertEqual(len(ldap3_manager._server_pool.servers), 1)

server = ldap3_manager._server_pool.servers[-1]
self.assertEqual(server.tls, None)
self.assertTrue(server.use_ssl)

@mock.patch('ldap3.Server', new=Server)
@mock.patch('ldap3.ServerPool', new=ServerPool)
def test_server_with_tls_with_ssl(self):
"""
Ensures a server is created/added to the pool, however that the server
was instantiated with `tls=<TLS CTX OBJECT>` and use_ssl=True.
"""
fake_tls_ctx = Tls()

ldap3_manager = ldap3_login.LDAP3LoginManager()
ldap3_manager.init_config(AddServerTestCase.DEFAULT_CONFIG)
ldap3_manager.add_server("ad2.mydomain.com", 389,
use_ssl=True, tls_ctx=fake_tls_ctx)

self.assertEqual(len(ldap3_manager._server_pool.servers), 1)

server = ldap3_manager._server_pool.servers[-1]
self.assertEqual(server.tls, fake_tls_ctx)
self.assertTrue(server.use_ssl)
71 changes: 71 additions & 0 deletions ldap_noapp_tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from flask_ldap3_login import LDAP3LoginManager
from ldap3 import Tls
import ssl

config = dict()

# Setup LDAP Configuration Variables. Change these to your own settings.
# All configuration directives can be found in the documentation.

# Hostname of your LDAP Server
config['LDAP_HOST'] = 'ad.mydomain.com'

# Port number of your LDAP server
config['LDAP_PORT'] = 636

# Base DN of your directory
config['LDAP_BASE_DN'] = 'dc=mydomain,dc=com'

# Users DN to be prepended to the Base DN
config['LDAP_USER_DN'] = 'ou=users'

# Groups DN to be prepended to the Base DN
config['LDAP_GROUP_DN'] = 'ou=groups'


# The RDN attribute for your user schema on LDAP
config['LDAP_USER_RDN_ATTR'] = 'cn'

# The Attribute you want users to authenticate to LDAP with.
config['LDAP_USER_LOGIN_ATTR'] = 'mail'

# The Username to bind to LDAP with
config['LDAP_BIND_USER_DN'] = None

# The Password to bind to LDAP with
config['LDAP_BIND_USER_PASSWORD'] = None

# Specify the server connection should use SSL
config['LDAP_USE_SSL'] = True

# Instruct Flask-LDAP3-Login to not automatically add the server
config['LDAP_ADD_SERVER'] = False

# Setup a LDAP3 Login Manager.
ldap_manager = LDAP3LoginManager()

# Init the mamager with the config since we aren't using an app
ldap_manager.init_config(config)


# Initialize a `Tls` context, and add the server manually. See
# http://ldap3.readthedocs.io/ssltls.html for more information.
tls_ctx = Tls(
validate=ssl.CERT_REQUIRED,
version=ssl.PROTOCOL_TLSv1,
ca_certs_file='/path/to/cacerts',
valid_names=[
'ad.mydomain.com',
]
)

ldap_manager.add_server(
config.get('LDAP_HOST'),
config.get('LDAP_PORT'),
config.get('LDAP_USE_SSL'),
tls_ctx=tls_ctx
)

# Check if the credentials are correct
response = ldap_manager.authenticate('username', 'password')
print(response.status)

0 comments on commit f2a2b45

Please sign in to comment.