Flask-LDAPConn-0.7.1/0000775000175000017500000000000013252763335014066 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.1/LICENSE0000664000175000017500000000251613252763163015076 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.1/setup.cfg0000664000175000017500000000004613252763335015707 0ustar rroerroe00000000000000[egg_info] tag_build = tag_date = 0 Flask-LDAPConn-0.7.1/Flask_LDAPConn.egg-info/0000775000175000017500000000000013252763335020236 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.1/Flask_LDAPConn.egg-info/PKG-INFO0000664000175000017500000001776513252763335021353 0ustar rroerroe00000000000000Metadata-Version: 1.1 Name: Flask-LDAPConn Version: 0.7.1 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_TIMEOUT = 10 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' TIf you want to allways get the entry attribute value as a list, instead of a string if only one item is in the attribute set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Create the ldap instance within 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 save a new connection to ``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.1/Flask_LDAPConn.egg-info/SOURCES.txt0000664000175000017500000000053113252763335022121 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.1/Flask_LDAPConn.egg-info/dependency_links.txt0000664000175000017500000000000113252763335024304 0ustar rroerroe00000000000000 Flask-LDAPConn-0.7.1/Flask_LDAPConn.egg-info/top_level.txt0000664000175000017500000000001713252763335022766 0ustar rroerroe00000000000000flask_ldapconn Flask-LDAPConn-0.7.1/Flask_LDAPConn.egg-info/requires.txt0000664000175000017500000000004113252763335022631 0ustar rroerroe00000000000000Flask>=0.12 ldap3>=2.3 six>=1.10 Flask-LDAPConn-0.7.1/PKG-INFO0000664000175000017500000001776513252763335015203 0ustar rroerroe00000000000000Metadata-Version: 1.1 Name: Flask-LDAPConn Version: 0.7.1 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_TIMEOUT = 10 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' TIf you want to allways get the entry attribute value as a list, instead of a string if only one item is in the attribute set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Create the ldap instance within 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 save a new connection to ``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.1/setup.py0000664000175000017500000000244113252762460015577 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.1', 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.1/flask_ldapconn/0000775000175000017500000000000013252763335017044 5ustar rroerroe00000000000000Flask-LDAPConn-0.7.1/flask_ldapconn/__init__.py0000664000175000017500000001472013252756677021174 0ustar rroerroe00000000000000# -*- coding: utf-8 -*- import ssl from flask import current_app, g from flask import _app_ctx_stack as 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_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_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'], 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'], user=user, password=password, check_names=True, read_only=current_app.config['LDAP_READ_ONLY'], ) return ldap_conn def teardown(self, exception): if hasattr(g, 'ldap_conn'): g.ldap_conn.unbind() ctx = stack.top if hasattr(ctx, 'ldap_conn'): ctx.ldap_conn.unbind() @property def connection(self): if hasattr(g, 'ldap_conn'): return g.ldap_conn 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.1/flask_ldapconn/attribute.py0000664000175000017500000000552413252757432021430 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.1/flask_ldapconn/query.py0000664000175000017500000000442213200713371020551 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.1/flask_ldapconn/entry.py0000664000175000017500000001460413252755054020563 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): json_entry = dict() json_entry['dn'] = self.dn 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.1/CHANGELOG.rst0000664000175000017500000000524513252763146016115 0ustar rroerroe00000000000000Changelog ========= 0.7.1 (2018-03-16) ------------------ * 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.1/README.rst0000664000175000017500000001272613252760372015563 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_TIMEOUT = 10 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' TIf you want to allways get the entry attribute value as a list, instead of a string if only one item is in the attribute set: .. code-block:: python FORCE_ATTRIBUTE_VALUE_AS_LIST = True Create the ldap instance within 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 save a new connection to ``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.1/MANIFEST.in0000664000175000017500000000005113200643766015616 0ustar rroerroe00000000000000include README.rst CHANGELOG.rst LICENSE