Flask-LDAPConn-0.7.2/0000755000175000017500000000000013310505533014052 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.2/LICENSE0000644000175000017500000000251613306246234015070 0ustar rroerroe00000000000000Copyright (c) 2017-2018, Rafael Römhild Copyright (c) 2017, Dominik George All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Flask-LDAPConn-0.7.2/setup.cfg0000644000175000017500000000004613310505533015673 0ustar rroerroe00000000000000[egg_info] tag_build = tag_date = 0 Flask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/0000755000175000017500000000000013310505533020222 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/PKG-INFO0000664000175000017500000002034013310505533021320 0ustar rroerroe00000000000000Metadata-Version: 1.1 Name: Flask-LDAPConn Version: 0.7.2 Summary: Pure python, LDAP connection and ORM for Flask Applications Home-page: http://github.com/rroemhild/flask-ldapconn Author: Rafael Römhild Author-email: rafael@roemhild.de License: BSD Description: Flask-LDAPConn ============== .. image:: https://travis-ci.org/rroemhild/flask-ldapconn.svg?branch=master :target: https://travis-ci.org/rroemhild/flask-ldapconn .. image:: https://badge.fury.io/py/Flask-LDAPConn.svg :target: https://badge.fury.io/py/Flask-LDAPConn Flask-LDAPConn is a Flask extension providing `ldap3 `_ (an LDAP V3 pure Python client) connection for accessing LDAP servers. To abstract access to LDAP data this extension provides a simple ORM model. Installation ------------ .. code-block:: shell pip install flask-ldapconn Configuration ------------- Your configuration should be declared within your Flask config. Sample configuration: .. code-block:: python import ssl LDAP_SERVER = 'localhost' LDAP_PORT = 389 LDAP_BINDDN = 'cn=admin,dc=example,dc=com' LDAP_SECRET = 'forty-two' LDAP_CONNECT_TIMEOUT = 10 # Honored when the TCP connection is being established LDAP_USE_TLS = True # default LDAP_REQUIRE_CERT = ssl.CERT_NONE # default: CERT_REQUIRED LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2 # default: PROTOCOL_TLSv1 LDAP_CERT_PATH = '/etc/openldap/certs' If you want to always get any entry attribute value as a list, instead of a string if only one item is in the attribute list, then set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Default is ``False`` and will return a string if only one item is in the attribute list. Setup ----- Create the LDAP instance in your application. .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) Client sample ------------- .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn from ldap3 import SUBTREE app = Flask(__name__) ldap = LDAPConn(app) @app.route('/') def index(): ldapc = ldap.connection basedn = 'ou=people,dc=example,dc=com' search_filter = '(objectClass=posixAccount)' attributes = ['sn', 'givenName', 'uid', 'mail'] ldapc.search(basedn, search_filter, SUBTREE, attributes=attributes) response = ldapc.response User model samples ------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) class User(ldap.Entry): base_dn = 'ou=people,dc=example,dc=com' object_classes = ['inetOrgPerson'] name = ldap.Attribute('cn') email = ldap.Attribute('mail') userid = ldap.Attribute('uid') surname = ldap.Attribute('sn') givenname = ldap.Attribute('givenName') with app.app_context(): # get a list of entries entries = User.query.filter('email: *@example.com').all() for entry in entries: print u'Name: {}'.format(entry.name) # get the first entry user = User.query.filter('userid: user1').first() # new entry new_user = User( name='User Three', email='user3@example.com', userid='user3', surname='Three', givenname='User' ) new_user.save() # modify entry mod_user = User.query.filter('userid: user1').first() mod_user.name = 'User Number Three' mod_user.email.append.('u.three@example.com') mod_user.givenname.delete() mod_user.save() # remove entry rm_user = User.query.filter('userid: user1').first() rm_user.delete() # authenticate user auth_user = User.query.filter('userid: user1').first() if auth_user: if auth_user.authenticate('password1234'): print('Authenticated') else: print('Wrong password') Authenticate with Client ------------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) username = 'user1' password = 'userpass' attribute = 'uid' search_filter = ('(active=1)') with app.app_context(): retval = ldap.authenticate(username, password, attribute, basedn, search_filter) if not retval: return 'Invalid credentials.' return 'Welcome %s.' % username Bind as user ------------ To bind as user for the current request instance a new connection from ``flask.g.ldap_conn``: .. code-block:: python g.ldap_conn = ldap.connect(userdn, password) user = User.query.get(userdn) Unit Test --------- I use a simple Docker image to run the tests on localhost. The test file ``test_flask_ldapconn.py`` tries to handle ``start`` and ``stop`` of the docker container: .. code-block:: shell pip install docker-py docker pull rroemhild/test-openldap python test_flask_ldapconn.py Run the docker container manual: .. code-block:: shell docker run --privileged -d -p 389:389 --name flask_ldapconn rroemhild/test-openldap DOCKER_RUN=False python test_flask_ldapconn.py Unit test with your own settings from a file: .. code-block:: shell LDAP_SETTINGS=my_settings.py python test_flask_ldapconn.py Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. Fork `the repository`_ on Github to start making your changes. #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. .. _`the repository`: http://github.com/rroemhild/flask-ldapconn Keywords: flask ldap ldap3 orm Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Framework :: Flask Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Flask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/SOURCES.txt0000664000175000017500000000053113310505533022107 0ustar rroerroe00000000000000CHANGELOG.rst LICENSE MANIFEST.in README.rst setup.py Flask_LDAPConn.egg-info/PKG-INFO Flask_LDAPConn.egg-info/SOURCES.txt Flask_LDAPConn.egg-info/dependency_links.txt Flask_LDAPConn.egg-info/requires.txt Flask_LDAPConn.egg-info/top_level.txt flask_ldapconn/__init__.py flask_ldapconn/attribute.py flask_ldapconn/entry.py flask_ldapconn/query.pyFlask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/dependency_links.txt0000664000175000017500000000000113310505533024272 0ustar rroerroe00000000000000 Flask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/top_level.txt0000664000175000017500000000001713310505533022754 0ustar rroerroe00000000000000flask_ldapconn Flask-LDAPConn-0.7.2/Flask_LDAPConn.egg-info/requires.txt0000664000175000017500000000004113310505533022617 0ustar rroerroe00000000000000Flask>=0.12 ldap3>=2.3 six>=1.10 Flask-LDAPConn-0.7.2/PKG-INFO0000644000175000017500000002034013310505533015146 0ustar rroerroe00000000000000Metadata-Version: 1.1 Name: Flask-LDAPConn Version: 0.7.2 Summary: Pure python, LDAP connection and ORM for Flask Applications Home-page: http://github.com/rroemhild/flask-ldapconn Author: Rafael Römhild Author-email: rafael@roemhild.de License: BSD Description: Flask-LDAPConn ============== .. image:: https://travis-ci.org/rroemhild/flask-ldapconn.svg?branch=master :target: https://travis-ci.org/rroemhild/flask-ldapconn .. image:: https://badge.fury.io/py/Flask-LDAPConn.svg :target: https://badge.fury.io/py/Flask-LDAPConn Flask-LDAPConn is a Flask extension providing `ldap3 `_ (an LDAP V3 pure Python client) connection for accessing LDAP servers. To abstract access to LDAP data this extension provides a simple ORM model. Installation ------------ .. code-block:: shell pip install flask-ldapconn Configuration ------------- Your configuration should be declared within your Flask config. Sample configuration: .. code-block:: python import ssl LDAP_SERVER = 'localhost' LDAP_PORT = 389 LDAP_BINDDN = 'cn=admin,dc=example,dc=com' LDAP_SECRET = 'forty-two' LDAP_CONNECT_TIMEOUT = 10 # Honored when the TCP connection is being established LDAP_USE_TLS = True # default LDAP_REQUIRE_CERT = ssl.CERT_NONE # default: CERT_REQUIRED LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2 # default: PROTOCOL_TLSv1 LDAP_CERT_PATH = '/etc/openldap/certs' If you want to always get any entry attribute value as a list, instead of a string if only one item is in the attribute list, then set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Default is ``False`` and will return a string if only one item is in the attribute list. Setup ----- Create the LDAP instance in your application. .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) Client sample ------------- .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn from ldap3 import SUBTREE app = Flask(__name__) ldap = LDAPConn(app) @app.route('/') def index(): ldapc = ldap.connection basedn = 'ou=people,dc=example,dc=com' search_filter = '(objectClass=posixAccount)' attributes = ['sn', 'givenName', 'uid', 'mail'] ldapc.search(basedn, search_filter, SUBTREE, attributes=attributes) response = ldapc.response User model samples ------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) class User(ldap.Entry): base_dn = 'ou=people,dc=example,dc=com' object_classes = ['inetOrgPerson'] name = ldap.Attribute('cn') email = ldap.Attribute('mail') userid = ldap.Attribute('uid') surname = ldap.Attribute('sn') givenname = ldap.Attribute('givenName') with app.app_context(): # get a list of entries entries = User.query.filter('email: *@example.com').all() for entry in entries: print u'Name: {}'.format(entry.name) # get the first entry user = User.query.filter('userid: user1').first() # new entry new_user = User( name='User Three', email='user3@example.com', userid='user3', surname='Three', givenname='User' ) new_user.save() # modify entry mod_user = User.query.filter('userid: user1').first() mod_user.name = 'User Number Three' mod_user.email.append.('u.three@example.com') mod_user.givenname.delete() mod_user.save() # remove entry rm_user = User.query.filter('userid: user1').first() rm_user.delete() # authenticate user auth_user = User.query.filter('userid: user1').first() if auth_user: if auth_user.authenticate('password1234'): print('Authenticated') else: print('Wrong password') Authenticate with Client ------------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) username = 'user1' password = 'userpass' attribute = 'uid' search_filter = ('(active=1)') with app.app_context(): retval = ldap.authenticate(username, password, attribute, basedn, search_filter) if not retval: return 'Invalid credentials.' return 'Welcome %s.' % username Bind as user ------------ To bind as user for the current request instance a new connection from ``flask.g.ldap_conn``: .. code-block:: python g.ldap_conn = ldap.connect(userdn, password) user = User.query.get(userdn) Unit Test --------- I use a simple Docker image to run the tests on localhost. The test file ``test_flask_ldapconn.py`` tries to handle ``start`` and ``stop`` of the docker container: .. code-block:: shell pip install docker-py docker pull rroemhild/test-openldap python test_flask_ldapconn.py Run the docker container manual: .. code-block:: shell docker run --privileged -d -p 389:389 --name flask_ldapconn rroemhild/test-openldap DOCKER_RUN=False python test_flask_ldapconn.py Unit test with your own settings from a file: .. code-block:: shell LDAP_SETTINGS=my_settings.py python test_flask_ldapconn.py Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. Fork `the repository`_ on Github to start making your changes. #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. .. _`the repository`: http://github.com/rroemhild/flask-ldapconn Keywords: flask ldap ldap3 orm Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Framework :: Flask Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules Flask-LDAPConn-0.7.2/setup.py0000644000175000017500000000244113310504337015566 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- ''' Flask-LDAPConn -------------- Flask extension providing ldap3 connection object and ORM to accessing LDAP servers. ''' from setuptools import setup setup( name='Flask-LDAPConn', version='0.7.2', url='http://github.com/rroemhild/flask-ldapconn', license='BSD', author='Rafael Römhild', author_email='rafael@roemhild.de', keywords='flask ldap ldap3 orm', description='Pure python, LDAP connection and ORM for Flask Applications', long_description=open('README.rst').read(), packages=[ 'flask_ldapconn' ], platforms='any', install_requires=[ 'Flask>=0.12', 'ldap3>=2.3', 'six>=1.10' ], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Framework :: Flask', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], ) Flask-LDAPConn-0.7.2/flask_ldapconn/0000755000175000017500000000000013310505533017030 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.2/flask_ldapconn/__init__.py0000644000175000017500000001501213310504326021137 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- import ssl from flask import current_app, _app_ctx_stack from ldap3 import Server, Connection, Tls from ldap3 import SYNC, ALL, SUBTREE from ldap3 import AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND from ldap3.core.exceptions import (LDAPBindError, LDAPInvalidFilterError, LDAPInvalidDnError) from ldap3.utils.dn import parse_dn from .entry import LDAPEntry from .attribute import LDAPAttribute __all__ = ('LDAPConn',) class LDAPConn(object): def __init__(self, app=None): self.Entry = LDAPEntry self.Attribute = LDAPAttribute self.Model = self.Entry self.app = app if app is not None: self.init_app(app) def init_app(self, app): ssl_defaults = ssl.get_default_verify_paths() # Default config app.config.setdefault('LDAP_SERVER', 'localhost') app.config.setdefault('LDAP_PORT', 389) app.config.setdefault('LDAP_BINDDN', None) app.config.setdefault('LDAP_SECRET', None) app.config.setdefault('LDAP_CONNECT_TIMEOUT', 10) app.config.setdefault('LDAP_READ_ONLY', False) app.config.setdefault('LDAP_VALID_NAMES', None) app.config.setdefault('LDAP_PRIVATE_KEY_PASSWORD', None) app.config.setdefault('LDAP_RAISE_EXCEPTIONS', False) app.config.setdefault('LDAP_CONNECTION_STRATEGY', SYNC) app.config.setdefault('LDAP_USE_SSL', False) app.config.setdefault('LDAP_USE_TLS', True) app.config.setdefault('LDAP_TLS_VERSION', ssl.PROTOCOL_TLSv1) app.config.setdefault('LDAP_REQUIRE_CERT', ssl.CERT_REQUIRED) app.config.setdefault('LDAP_CLIENT_PRIVATE_KEY', None) app.config.setdefault('LDAP_CLIENT_CERT', None) app.config.setdefault('LDAP_CA_CERTS_FILE', ssl_defaults.cafile) app.config.setdefault('LDAP_CA_CERTS_PATH', ssl_defaults.capath) app.config.setdefault('LDAP_CA_CERTS_DATA', None) app.config.setdefault('FORCE_ATTRIBUTE_VALUE_AS_LIST', False) self.tls = Tls( local_private_key_file=app.config['LDAP_CLIENT_PRIVATE_KEY'], local_certificate_file=app.config['LDAP_CLIENT_CERT'], validate=app.config['LDAP_REQUIRE_CERT'], version=app.config['LDAP_TLS_VERSION'], ca_certs_file=app.config['LDAP_CA_CERTS_FILE'], valid_names=app.config['LDAP_VALID_NAMES'], ca_certs_path=app.config['LDAP_CA_CERTS_PATH'], ca_certs_data=app.config['LDAP_CA_CERTS_DATA'], local_private_key_password=app.config['LDAP_PRIVATE_KEY_PASSWORD'] ) self.ldap_server = Server( host=app.config['LDAP_SERVER'], port=app.config['LDAP_PORT'], use_ssl=app.config['LDAP_USE_SSL'], connect_timeout=app.config['LDAP_CONNECT_TIMEOUT'], tls=self.tls, get_info=ALL ) # Store ldap_conn object to extensions app.extensions['ldap_conn'] = self # Teardown appcontext app.teardown_appcontext(self.teardown) def connect(self, user, password): auto_bind_strategy = AUTO_BIND_TLS_BEFORE_BIND if current_app.config['LDAP_USE_TLS'] is not True: auto_bind_strategy = AUTO_BIND_NO_TLS ldap_conn = Connection( self.ldap_server, auto_bind=auto_bind_strategy, client_strategy=current_app.config['LDAP_CONNECTION_STRATEGY'], raise_exceptions=current_app.config['LDAP_RAISE_EXCEPTIONS'], user=user, password=password, check_names=True, read_only=current_app.config['LDAP_READ_ONLY'], ) return ldap_conn def teardown(self, exception): ctx = _app_ctx_stack.top if hasattr(ctx, 'ldap_conn'): ctx.ldap_conn.unbind() @property def connection(self): ctx = _app_ctx_stack.top if ctx is not None: if not hasattr(ctx, 'ldap_conn'): ctx.ldap_conn = self.connect( current_app.config['LDAP_BINDDN'], current_app.config['LDAP_SECRET'] ) return ctx.ldap_conn def authenticate(self, username, password, attribute=None, base_dn=None, search_filter=None, search_scope=SUBTREE): '''Attempts to bind a user to the LDAP server. Args: username (str): DN or the username to attempt to bind with. password (str): The password of the username. attribute (str): The LDAP attribute for the username. base_dn (str): The LDAP basedn to search on. search_filter (str): LDAP searchfilter to attempt the user search with. Returns: bool: ``True`` if successful or ``False`` if the credentials are invalid. ''' # If the username is no valid DN we can bind with, we need to find # the user first. valid_dn = False try: parse_dn(username) valid_dn = True except LDAPInvalidDnError: pass if valid_dn is False: user_filter = '({0}={1})'.format(attribute, username) if search_filter is not None: user_filter = '(&{0}{1})'.format(user_filter, search_filter) try: self.connection.search(base_dn, user_filter, search_scope, attributes=[attribute]) response = self.connection.response username = response[0]['dn'] except (LDAPInvalidDnError, LDAPInvalidFilterError, IndexError): return False try: conn = self.connect(username, password) conn.unbind() return True except LDAPBindError: return False def whoami(self): '''Deprecated Use LDAPConn.connection.extend.standard.who_am_i() ''' return self.connection.extend.standard.who_am_i() def result(self): '''Deprecated Use LDAPConn.connection.result ''' return self.connection.result def response(self): '''Deprecated Use LDAPConn.connection.response ''' return self.connection.response def search(self, *args, **kwargs): '''Deprecated Use LDAPConn.connection.search() ''' return self.connection.search(*args, **kwargs) Flask-LDAPConn-0.7.2/flask_ldapconn/attribute.py0000644000175000017500000000552413306246234021420 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- from flask import current_app from ldap3 import AttrDef from ldap3.core.exceptions import LDAPAttributeError from ldap3 import (STRING_TYPES, NUMERIC_TYPES, MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE) class LDAPAttribute(object): def __init__(self, name, validate=None, default=None, dereference_dn=None): self.__dict__['name'] = name self.__dict__['values'] = [] self.__dict__['default'] = default self.__dict__['validate'] = validate self.__dict__['changetype'] = None self.__dict__['dereference_dn'] = dereference_dn def __str__(self): if isinstance(self.value, STRING_TYPES): return self.value else: return str(self.value) def __len__(self): return len(self.values) def __iter__(self): return self.values.__iter__() def __contains__(self, item): return item in self.__dict__['values'] def __setattr__(self, item, value): if item not in ['value', '_init']: raise LDAPAttributeError('can not set key') # set changetype if item == 'value': if self.__dict__['values']: if not value: self.__dict__['changetype'] = MODIFY_DELETE else: self.__dict__['changetype'] = MODIFY_REPLACE else: self.__dict__['changetype'] = MODIFY_ADD if isinstance(value, (STRING_TYPES, NUMERIC_TYPES)): value = [value] self.__dict__['values'] = value @property def value(self): '''Return single value or list of values from the attribute. If FORCE_ATTRIBUTE_VALUE_AS_LIST is True, always return a list with values. ''' if len(self.__dict__['values']) == 1 and current_app.config['FORCE_ATTRIBUTE_VALUE_AS_LIST'] is False: return self.__dict__['values'][0] else: return self.__dict__['values'] @property def changetype(self): return self.__dict__['changetype'] def get_changes_tuple(self): values = [val.encode('UTF-8') for val in self.__dict__['values']] return (self.changetype, values) def append(self, value): '''Add another value to the attribute''' if self.__dict__['values']: self.__dict__['changetype'] = MODIFY_REPLACE self.__dict__['values'].append(value) def delete(self): '''Delete this attribute This property sets the value to an empty list an the changetype to delete. ''' self.value = [] def get_abstract_attr_def(self, key): return AttrDef(name=self.name, key=key, validate=self.validate, default=self.default, dereference_dn=self.dereference_dn) Flask-LDAPConn-0.7.2/flask_ldapconn/query.py0000644000175000017500000000442213305225645020560 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- from flask import current_app from ldap3 import BASE, Reader __all__ = ('BaseQuery',) class BaseQuery(object): def __init__(self, type): self.type = type self.query = [] self.base_dn = getattr(type, 'base_dn') self.sub_tree = getattr(type, 'sub_tree') self.object_def = getattr(type, '_object_def') self.components_in_and = True self.operational_attributes = getattr(type, 'operational_attributes') def __iter__(self): for entry in self.get_reader_result(): new_cls = self.type.get_new_type() ldapentry = new_cls(dn=entry.entry_dn, changetype='modify', **entry.entry_attributes_as_dict) yield ldapentry def get_reader_result(self): query = ','.join(self.query) ldapc = current_app.extensions.get('ldap_conn') reader = Reader(connection=ldapc.connection, object_def=self.object_def, query=query, base=self.base_dn, components_in_and=self.components_in_and, sub_tree=self.sub_tree, get_operational_attributes=self.operational_attributes, controls=None) reader.search() return reader.entries def get(self, ldap_dn): '''Return an LDAP entry by DN Args: ldap_dn (str): LDAP DN ''' self.base_dn = ldap_dn self.sub_tree = BASE return self.first() def filter(self, *query_filter): '''Set the query filter to perform the query with Args: *query_filter: Simplified Query Language filter ''' for query in query_filter: self.query.append(query) return self def first(self): '''Execute the query and return the first result If there are no entries, first returns ``None`` ''' for entry in iter(self): return entry return None def all(self, components_in_and=True): '''Return all of the results of a query in a list''' self.components_in_and = components_in_and return [obj for obj in iter(self)] Flask-LDAPConn-0.7.2/flask_ldapconn/entry.py0000644000175000017500000001550413306246234020555 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- import json from six import add_metaclass from copy import deepcopy from importlib import import_module from flask import current_app from ldap3 import ObjectDef from ldap3.core.exceptions import LDAPAttributeError from ldap3.utils.dn import safe_dn from ldap3.utils.conv import check_json_dict, format_json from .query import BaseQuery from .attribute import LDAPAttribute __all__ = ('LDAPEntry',) class LDAPEntryMeta(type): # requiered base_dn = None entry_rdn = ['cn'] object_classes = ['top'] # optional sub_tree = True operational_attributes = False def __init__(cls, name, bases, ns): cls._attributes = dict() # Merge attributes and object classes from parents for base in bases: if isinstance(base, LDAPEntryMeta): cls._attributes.update(base._attributes) # Deduplicate object classes cls.object_classes = list( set(cls.object_classes + base.object_classes)) # Create object definition cls._object_def = ObjectDef(cls.object_classes) # loop through the namespace looking for LDAPAttribute instances for key, value in ns.items(): if isinstance(value, LDAPAttribute): cls._attributes[key] = value # Generate attribute definitions for key in cls._attributes: attr_def = cls._attributes[key].get_abstract_attr_def(key) cls._object_def.add_attribute(attr_def) @property def query(cls): return BaseQuery(cls) def get_new_type(cls): class_dict = deepcopy(cls()._attributes) module = import_module(cls.__module__) obj = getattr(module, cls.__name__) new_cls = type(cls.__name__, (obj,), class_dict) return new_cls @add_metaclass(LDAPEntryMeta) class LDAPEntry(object): def __init__(self, dn=None, changetype='add', **kwargs): self.__dict__['_dn'] = dn self.__dict__['_changetype'] = changetype for key, value in kwargs.items(): if key not in self._attributes: raise LDAPAttributeError('attribute not found') self._attributes[key]._init = value def __iter__(self): for attribute in self._attributes: yield self._attributes[attribute] def __contains__(self, item): return item in self._attributes def __getitem__(self, item): if item in self.__attributes: return self.__getattr__(item) else: raise KeyError(item) def __getattribute__(self, item): if item != '_attributes' and item in self._attributes: return self._attributes[item].value else: return object.__getattribute__(self, item) def __setitem__(self, key, value): if key in self._attributes: self.__setattr__(key, value) else: raise KeyError(key) def __setattr__(self, key, value): if key in self._attributes: self._attributes[key].value = value else: return object.__setattr__(self, key, value) @property def dn(self): if self._dn is None: self.generate_dn_from_entry() return self._dn def generate_dn_from_entry(self): rdn_list = list() for attr in self._object_def: if attr.name in self.entry_rdn: if len(self._attributes[attr.key]) == 1: rdn = '{attr}={value}'.format( attr=attr.name, value=self._attributes[attr.key].value ) rdn_list.append(rdn) dn = '{rdn},{base_dn}'.format(rdn='+'.join(rdn_list), base_dn=self.base_dn) self.__dict__['_dn'] = safe_dn(dn) def get_attributes_dict(self): return dict((attribute_key, attribute_value.values) for (attribute_key, attribute_value) in self._attributes.items()) def get_entry_add_dict(self, attr_dict): add_dict = dict() for attribute_key, attribute_value in attr_dict.items(): if self._attributes[attribute_key].value: attribute_def = self._object_def[attribute_key] add_dict.update({attribute_def.name: attribute_value}) return add_dict def get_entry_modify_dict(self, attr_dict): modify_dict = dict() for attribute_key in attr_dict.keys(): if self._attributes[attribute_key].changetype is not None: attribute_def = self._object_def[attribute_key] changes = self._attributes[attribute_key].get_changes_tuple() modify_dict.update({attribute_def.name: changes}) return modify_dict @property def connection(self): return current_app.extensions.get('ldap_conn') def delete(self): '''Delete this entry from LDAP server''' self.connection.connection.delete(self.dn) def save(self): '''Save the current instance''' attributes = self.get_entry_add_dict(self.get_attributes_dict()) if self._changetype == 'add': return self.connection.connection.add(self.dn, self.object_classes, attributes) elif self._changetype == 'modify': changes = self.get_entry_modify_dict(self.get_attributes_dict()) return self.connection.connection.modify(self.dn, changes) return False def authenticate(self, password): '''Authenticate a user with an LDAPModel class Args: password (str): The user password. ''' return self.connection.authenticate(self.dn, password) def to_json(self, indent=2, sort=True, str_values=False): json_entry = dict() json_entry['dn'] = self.dn # Get "single values" from attributes as str instead list if # `str_values=True` else get all attributes as list. This only # works if `FORCE_ATTRIBUTE_VALUE_AS_LIST` is False (default). if str_values is True: json_entry['attributes'] = {} for attr in self._attributes.keys(): json_entry['attributes'][attr] = self._attributes[attr].value else: json_entry['attributes'] = self.get_attributes_dict() if str == bytes: check_json_dict(json_entry) json_output = json.dumps(json_entry, ensure_ascii=True, sort_keys=sort, indent=indent, check_circular=True, default=format_json, separators=(',', ': ')) return json_output LDAPModel = LDAPEntry Flask-LDAPConn-0.7.2/CHANGELOG.rst0000644000175000017500000000556113310504622016100 0ustar rroerroe00000000000000Changelog ========= 0.7.2 (2018-06-14) ------------------ * Add support to return string values in JSON * Add support for LDAP_RAISE_EXCEPTIONS (Robert Wikman) * Rename LDAP_TIMEOUT to LDAP_CONNECT_TIMEOUT (Robert Wikman) 0.7.1 (2018-04-07) ------------------ * Add setting FORCE_ATTRIBUTE_VALUE_AS_LIST * Add Pipfile and Pipfile.lock for pipenv * Add Python 3.5 & 3.6 to unittest 0.7.0 (2017-11-09) ------------------ * Allow model inheritance (Dominik George) * Fix/revisit attribute access (Dominik George) * Update ldap3 to version 2.3 * Update Flaks to 0.12 0.6.13 (2016-05-30) ------------------- * Fix get entries with multivalued RDNs * Update ldap3 to version 1.3.1 0.6.12 (2016-04-03) ------------------- * Update ldap3 to version 1.2.2 * Dropped support for Python 3.3 0.6.11 (2016-01-28) ------------------- * Use components_in_and flag in Reader object * Update ldap3 to version 1.0.4 0.6.10 (2015-12-15) ------------------- * Update ldap3 to version 1.0.3 0.6.9 (2015-12-15) ------------------ * Update ldap3 to version 1.0.2 0.6.8 (2015-12-07) ------------------ * Add read-only option * Update ldap3 to version 1.0.1 0.6.7 (2015-10-11) ------------------ * Use connections saved on flask.g.ldap_conn 0.6.6 (2015-10-8) ------------------ * Return manager class in queries instead of fix LDAPEntry class * Update six 1.9.0 -> 1.10.0 0.6.5 ----- * Update ldap3 to version 0.9.9.1 0.6.4 (2015-08-16) ------------------ * Update ldap3 to version 0.9.8.8 0.6.3 (2015-07-07) ------------------ * Update ldap3 to version 0.9.8.6 0.6.2 (2015-06-21) ------------------ * Fix TLS settings 0.6.1 (2015-05-29) ------------------ * Update ldap3 to v0.9.8.4 0.6 (2015-03-31) ---------------- * Refactored the LDAPModel class * LDAPModel is now LDAPEntry * Add write operation save (add, modify) and delete * LDAPEntry now use a query class to simplify ldap query 0.5.2 (2015-03-11) ------------------ * LDAPModel classes can now be instantiated with arguments. 0.5.1 (2015-03-11) ------------------ * Fixed installer problem. Handle flask-ldapconn as package. * Refactored the LDAPModel class 0.5 (2015-03-07) ---------------- * Refactored the LDAPModel class 0.4 (2015-03-07) ---------------- * Add authentication method * Deprecate mapped connection methods * Update Flask to 0.10.1 and ldap3 to 0.9.7.10 0.3.4 ----- * v0.3.4: Add configuration option for SSL (Bartosz Marcinkowski) * v0.3.4: Add support for Python 3 (Bartosz Marcinkowski) * v0.3.4: Update python-ldap3 to v0.9.7.5 0.3.3 ----- * v0.3.3: Allow anonymous auth 0.3.2 ----- * v0.3.2: BUGFIX: Allow unsecure connections 0.3.1 ------ * v0.3.1: Return entries instead of Reader object in models 0.3 (2015-02-10) ---------------- * Add simple read-only class model 0.2 (2015-02-05) ---------------- * Switch to python-ldap3 0.1 (2015-02-02) ---------------- * Conception * Initial Commit of Package to GitHub Flask-LDAPConn-0.7.2/README.rst0000644000175000017500000001321113310504326015536 0ustar rroerroe00000000000000Flask-LDAPConn ============== .. image:: https://travis-ci.org/rroemhild/flask-ldapconn.svg?branch=master :target: https://travis-ci.org/rroemhild/flask-ldapconn .. image:: https://badge.fury.io/py/Flask-LDAPConn.svg :target: https://badge.fury.io/py/Flask-LDAPConn Flask-LDAPConn is a Flask extension providing `ldap3 `_ (an LDAP V3 pure Python client) connection for accessing LDAP servers. To abstract access to LDAP data this extension provides a simple ORM model. Installation ------------ .. code-block:: shell pip install flask-ldapconn Configuration ------------- Your configuration should be declared within your Flask config. Sample configuration: .. code-block:: python import ssl LDAP_SERVER = 'localhost' LDAP_PORT = 389 LDAP_BINDDN = 'cn=admin,dc=example,dc=com' LDAP_SECRET = 'forty-two' LDAP_CONNECT_TIMEOUT = 10 # Honored when the TCP connection is being established LDAP_USE_TLS = True # default LDAP_REQUIRE_CERT = ssl.CERT_NONE # default: CERT_REQUIRED LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2 # default: PROTOCOL_TLSv1 LDAP_CERT_PATH = '/etc/openldap/certs' If you want to always get any entry attribute value as a list, instead of a string if only one item is in the attribute list, then set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Default is ``False`` and will return a string if only one item is in the attribute list. Setup ----- Create the LDAP instance in your application. .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) Client sample ------------- .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn from ldap3 import SUBTREE app = Flask(__name__) ldap = LDAPConn(app) @app.route('/') def index(): ldapc = ldap.connection basedn = 'ou=people,dc=example,dc=com' search_filter = '(objectClass=posixAccount)' attributes = ['sn', 'givenName', 'uid', 'mail'] ldapc.search(basedn, search_filter, SUBTREE, attributes=attributes) response = ldapc.response User model samples ------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) class User(ldap.Entry): base_dn = 'ou=people,dc=example,dc=com' object_classes = ['inetOrgPerson'] name = ldap.Attribute('cn') email = ldap.Attribute('mail') userid = ldap.Attribute('uid') surname = ldap.Attribute('sn') givenname = ldap.Attribute('givenName') with app.app_context(): # get a list of entries entries = User.query.filter('email: *@example.com').all() for entry in entries: print u'Name: {}'.format(entry.name) # get the first entry user = User.query.filter('userid: user1').first() # new entry new_user = User( name='User Three', email='user3@example.com', userid='user3', surname='Three', givenname='User' ) new_user.save() # modify entry mod_user = User.query.filter('userid: user1').first() mod_user.name = 'User Number Three' mod_user.email.append.('u.three@example.com') mod_user.givenname.delete() mod_user.save() # remove entry rm_user = User.query.filter('userid: user1').first() rm_user.delete() # authenticate user auth_user = User.query.filter('userid: user1').first() if auth_user: if auth_user.authenticate('password1234'): print('Authenticated') else: print('Wrong password') Authenticate with Client ------------------------ .. code-block:: python from flask import Flask from flask_ldapconn import LDAPConn app = Flask(__name__) ldap = LDAPConn(app) username = 'user1' password = 'userpass' attribute = 'uid' search_filter = ('(active=1)') with app.app_context(): retval = ldap.authenticate(username, password, attribute, basedn, search_filter) if not retval: return 'Invalid credentials.' return 'Welcome %s.' % username Bind as user ------------ To bind as user for the current request instance a new connection from ``flask.g.ldap_conn``: .. code-block:: python g.ldap_conn = ldap.connect(userdn, password) user = User.query.get(userdn) Unit Test --------- I use a simple Docker image to run the tests on localhost. The test file ``test_flask_ldapconn.py`` tries to handle ``start`` and ``stop`` of the docker container: .. code-block:: shell pip install docker-py docker pull rroemhild/test-openldap python test_flask_ldapconn.py Run the docker container manual: .. code-block:: shell docker run --privileged -d -p 389:389 --name flask_ldapconn rroemhild/test-openldap DOCKER_RUN=False python test_flask_ldapconn.py Unit test with your own settings from a file: .. code-block:: shell LDAP_SETTINGS=my_settings.py python test_flask_ldapconn.py Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. Fork `the repository`_ on Github to start making your changes. #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. .. _`the repository`: http://github.com/rroemhild/flask-ldapconn Flask-LDAPConn-0.7.2/MANIFEST.in0000664000175000017500000000005113200643766015617 0ustar rroerroe00000000000000include README.rst CHANGELOG.rst LICENSE