matrix-synapse-ldap3-0.1.3/0000755000175000017500000000000013140173105015367 5ustar hexahexa00000000000000matrix-synapse-ldap3-0.1.3/ldap_auth_provider.py0000644000175000017500000003427513140172227021633 0ustar hexahexa00000000000000# -*- coding: utf-8 -*- # Copyright 2016 OpenMarket Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from twisted.internet import defer, threads import ldap3 import ldap3.core.exceptions import logging __version__ = "0.1.3" try: import ldap3 import ldap3.core.exceptions # ldap3 v2 changed ldap3.AUTH_SIMPLE -> ldap3.SIMPLE try: LDAP_AUTH_SIMPLE = ldap3.AUTH_SIMPLE except AttributeError: LDAP_AUTH_SIMPLE = ldap3.SIMPLE except ImportError: ldap3 = None pass logger = logging.getLogger(__name__) class LDAPMode(object): SIMPLE = "simple", SEARCH = "search", LIST = (SIMPLE, SEARCH) class LdapAuthProvider(object): __version__ = "0.1" def __init__(self, config, account_handler): self.account_handler = account_handler if not ldap3: raise RuntimeError( 'Missing ldap3 library. ' 'This is required for LDAP Authentication.' ) self.ldap_mode = config.mode self.ldap_uri = config.uri self.ldap_start_tls = config.start_tls self.ldap_base = config.base self.ldap_attributes = config.attributes if self.ldap_mode == LDAPMode.SEARCH: self.ldap_bind_dn = config.bind_dn self.ldap_bind_password = config.bind_password self.ldap_filter = config.filter @defer.inlineCallbacks def check_password(self, user_id, password): """ Attempt to authenticate a user against an LDAP Server and register an account if none exists. Returns: True if authentication against LDAP was successful """ if not password: defer.returnValue(False) # user_id is of the form @foo:bar.com localpart = user_id.split(":", 1)[0][1:] try: server = ldap3.Server(self.ldap_uri, get_info=None) logger.debug( "Attempting LDAP connection with %s", self.ldap_uri ) if self.ldap_mode == LDAPMode.SIMPLE: bind_dn = "{prop}={value},{base}".format( prop=self.ldap_attributes['uid'], value=localpart, base=self.ldap_base ) result, conn = yield self._ldap_simple_bind( server=server, bind_dn=bind_dn, password=password ) logger.debug( 'LDAP authentication method simple bind returned: ' '%s (conn: %s)', result, conn ) if not result: defer.returnValue(False) elif self.ldap_mode == LDAPMode.SEARCH: result, conn = yield self._ldap_authenticated_search( server=server, localpart=localpart, password=password ) logger.debug( 'LDAP auth method authenticated search returned: ' '%s (conn: %s)', result, conn ) if not result: defer.returnValue(False) else: raise RuntimeError( 'Invalid LDAP mode specified: {mode}'.format( mode=self.ldap_mode ) ) try: logger.info( "User authenticated against LDAP server: %s", conn ) except NameError: logger.warning( "Authentication method yielded no LDAP connection, " "aborting!" ) defer.returnValue(False) # check if user with user_id exists if (yield self.account_handler.check_user_exists(user_id)): # exists, authentication complete yield threads.deferToThread(conn.unbind) defer.returnValue(True) else: # does not exist, fetch metadata for account creation from # existing ldap connection query = "({prop}={value})".format( prop=self.ldap_attributes['uid'], value=localpart ) if self.ldap_mode == LDAPMode.SEARCH and self.ldap_filter: query = "(&{filter}{user_filter})".format( filter=query, user_filter=self.ldap_filter ) logger.debug( "ldap registration filter: %s", query ) yield threads.deferToThread( conn.search, search_base=self.ldap_base, search_filter=query, attributes=[ self.ldap_attributes['name'], self.ldap_attributes['mail'] ] ) responses = [ response for response in conn.response if response['type'] == 'searchResEntry' ] if len(responses) == 1: attrs = responses[0]['attributes'] name = attrs[self.ldap_attributes['name']][0] try: mail = attrs[self.ldap_attributes['mail']][0] except KeyError: mail = None # create account user_id, access_token = ( yield self.account_handler.register(localpart=localpart) ) # TODO: bind email, set displayname with data from # ldap directory logger.info( "Registration based on LDAP data was successful: " "%d: %s (%s, %)", user_id, localpart, name, mail ) defer.returnValue(True) else: if len(responses) == 0: logger.warning("LDAP registration failed, no result.") else: logger.warning( "LDAP registration failed, too many results (%s)", len(responses) ) defer.returnValue(False) defer.returnValue(False) except ldap3.core.exceptions.LDAPException as e: logger.warning("Error during ldap authentication: %s", e) defer.returnValue(False) @staticmethod def parse_config(config): class _LdapConfig(object): pass ldap_config = _LdapConfig() ldap_config.enabled = config.get("enabled", False) ldap_config.mode = LDAPMode.SIMPLE # verify config sanity _require_keys(config, [ "uri", "base", "attributes", ]) ldap_config.uri = config["uri"] ldap_config.start_tls = config.get("start_tls", False) ldap_config.base = config["base"] ldap_config.attributes = config["attributes"] if "bind_dn" in config: ldap_config.mode = LDAPMode.SEARCH _require_keys(config, [ "bind_dn", "bind_password", ]) ldap_config.bind_dn = config["bind_dn"] ldap_config.bind_password = config["bind_password"] ldap_config.filter = config.get("filter", None) # verify attribute lookup _require_keys(config['attributes'], [ "uid", "name", "mail", ]) return ldap_config @defer.inlineCallbacks def _ldap_simple_bind(self, server, bind_dn, password): """ Attempt a simple bind with the credentials given by the user against the LDAP server. Returns True, LDAP3Connection if the bind was successful Returns False, None if an error occured """ try: # bind with the the local users ldap credentials conn = yield threads.deferToThread( ldap3.Connection, server, bind_dn, password, authentication=LDAP_AUTH_SIMPLE, read_only=True, ) logger.debug( "Established LDAP connection in simple bind mode: %s", conn ) if self.ldap_start_tls: yield threads.deferToThread(conn.open) yield threads.deferToThread(conn.start_tls) logger.debug( "Upgraded LDAP connection in simple bind mode through " "StartTLS: %s", conn ) if (yield threads.deferToThread(conn.bind)): # GOOD: bind okay logger.debug("LDAP Bind successful in simple bind mode.") defer.returnValue((True, conn)) # BAD: bind failed logger.info( "Binding against LDAP failed for '%s' failed: %s", bind_dn, conn.result['description'] ) yield threads.deferToThread(conn.unbind) defer.returnValue((False, None)) except ldap3.core.exceptions.LDAPException as e: logger.warning("Error during LDAP authentication: %s", e) defer.returnValue((False, None)) @defer.inlineCallbacks def _ldap_authenticated_search(self, server, localpart, password): """ Attempt to login with the preconfigured bind_dn and then continue searching and filtering within the base_dn Returns (True, LDAP3Connection) if a single matching DN within the base was found that matched the filter expression, and with which a successful bind was achieved The LDAP3Connection returned is the instance that was used to verify the password not the one using the configured bind_dn. Returns (False, None) if an error occured """ try: conn = yield threads.deferToThread( ldap3.Connection, server, self.ldap_bind_dn, self.ldap_bind_password ) logger.debug( "Established LDAP connection in search mode: %s", conn ) if self.ldap_start_tls: yield threads.deferToThread(conn.open) yield threads.deferToThread(conn.start_tls) logger.debug( "Upgraded LDAP connection in search mode through " "StartTLS: %s", conn ) if not (yield threads.deferToThread(conn.bind)): logger.warning( "Binding against LDAP with `bind_dn` failed: %s", conn.result['description'] ) yield threads.deferToThread(conn.unbind) defer.returnValue((False, None)) # construct search_filter like (uid=localpart) query = "({prop}={value})".format( prop=self.ldap_attributes['uid'], value=localpart ) if self.ldap_filter: # combine with the AND expression query = "(&{query}{filter})".format( query=query, filter=self.ldap_filter ) logger.debug( "LDAP search filter: %s", query ) yield threads.deferToThread( conn.search, search_base=self.ldap_base, search_filter=query ) responses = [ response for response in conn.response if response['type'] == 'searchResEntry' ] if len(responses) == 1: # GOOD: found exactly one result user_dn = responses[0]['dn'] logger.debug('LDAP search found dn: %s', user_dn) # unbind and simple bind with user_dn to verify the password # Note: do not use rebind(), for some reason it did not verify # the password for me! yield threads.deferToThread(conn.unbind) result = yield self._ldap_simple_bind( server=server, bind_dn=user_dn, password=password ) defer.returnValue(result) else: # BAD: found 0 or > 1 results, abort! if len(responses) == 0: logger.info( "LDAP search returned no results for '%s'", localpart ) else: logger.info( "LDAP search returned too many (%s) results for '%s'", len(responses), localpart ) yield threads.deferToThread(conn.unbind) defer.returnValue((False, None)) except ldap3.core.exceptions.LDAPException as e: logger.warning("Error during LDAP authentication: %s", e) defer.returnValue((False, None)) def _require_keys(config, required): missing = [key for key in required if key not in config] if missing: raise Exception( "LDAP enabled but missing required config values: {}".format( ", ".join(missing) ) ) matrix-synapse-ldap3-0.1.3/LICENSE0000644000175000017500000002367613017075440016420 0ustar hexahexa00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS matrix-synapse-ldap3-0.1.3/MANIFEST.in0000644000175000017500000000016013017075440017130 0ustar hexahexa00000000000000include *.in include *.py include LICENSE include tox.ini include requirements.txt recursive-include tests *.py matrix-synapse-ldap3-0.1.3/PKG-INFO0000644000175000017500000000454213140173105016471 0ustar hexahexa00000000000000Metadata-Version: 1.1 Name: matrix-synapse-ldap3 Version: 0.1.3 Summary: An LDAP3 auth provider for Synapse Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: Synapse LDAP Auth Provider ========================== Allows synapse to use LDAP as a password provider. Installation ------------ - Via deb package `python-matrix-synapse-ldap3` available in the same repo as the synapse package - Via python's package manager: `pip install matrix-synapse-ldap3` Usage ----- Example synapse config: .. code:: yaml password_providers: - module: "ldap_auth_provider.LdapAuthProvider" config: enabled: true uri: "ldap://ldap.example.com:389" start_tls: true base: "ou=users,dc=example,dc=com" attributes: uid: "cn" mail: "email" name: "givenName" #bind_dn: #bind_password: #filter: "(objectClass=posixAccount)" Troubleshooting and Debugging ----------------------------- ``matrix-synapse-ldap3`` logging is included in the Synapse homeserver log (typically ``homeserver.log``). The LDAP plugin log level can be increased to ``DEBUG`` for troubleshooting and debugging by making the following modifications to your Synapse server's logging configuration file: - Set the value for `handlers.file.level` to `DEBUG`: .. code:: yaml handlers: file: # [...] level: DEBUG - Add the following to the `loggers` section: .. code:: yaml loggers: # [...] ldap3: level: DEBUG ldap_auth_provider: level: DEBUG Finally, restart your Synapse server for the changes to take effect: .. code:: sh synctl restart Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 2.7 matrix-synapse-ldap3-0.1.3/setup.cfg0000644000175000017500000000012313140173105017204 0ustar hexahexa00000000000000[flake8] max-line-length = 90 ignore = W503 [egg_info] tag_build = tag_date = 0 matrix-synapse-ldap3-0.1.3/README.rst0000644000175000017500000000275013140172154017065 0ustar hexahexa00000000000000Synapse LDAP Auth Provider ========================== Allows synapse to use LDAP as a password provider. Installation ------------ - Via deb package `python-matrix-synapse-ldap3` available in the same repo as the synapse package - Via python's package manager: `pip install matrix-synapse-ldap3` Usage ----- Example synapse config: .. code:: yaml password_providers: - module: "ldap_auth_provider.LdapAuthProvider" config: enabled: true uri: "ldap://ldap.example.com:389" start_tls: true base: "ou=users,dc=example,dc=com" attributes: uid: "cn" mail: "email" name: "givenName" #bind_dn: #bind_password: #filter: "(objectClass=posixAccount)" Troubleshooting and Debugging ----------------------------- ``matrix-synapse-ldap3`` logging is included in the Synapse homeserver log (typically ``homeserver.log``). The LDAP plugin log level can be increased to ``DEBUG`` for troubleshooting and debugging by making the following modifications to your Synapse server's logging configuration file: - Set the value for `handlers.file.level` to `DEBUG`: .. code:: yaml handlers: file: # [...] level: DEBUG - Add the following to the `loggers` section: .. code:: yaml loggers: # [...] ldap3: level: DEBUG ldap_auth_provider: level: DEBUG Finally, restart your Synapse server for the changes to take effect: .. code:: sh synctl restart matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/0000755000175000017500000000000013140173105023212 5ustar hexahexa00000000000000matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/top_level.txt0000644000175000017500000000002313140173105025737 0ustar hexahexa00000000000000ldap_auth_provider matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/PKG-INFO0000644000175000017500000000454213140173105024314 0ustar hexahexa00000000000000Metadata-Version: 1.1 Name: matrix-synapse-ldap3 Version: 0.1.3 Summary: An LDAP3 auth provider for Synapse Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: Synapse LDAP Auth Provider ========================== Allows synapse to use LDAP as a password provider. Installation ------------ - Via deb package `python-matrix-synapse-ldap3` available in the same repo as the synapse package - Via python's package manager: `pip install matrix-synapse-ldap3` Usage ----- Example synapse config: .. code:: yaml password_providers: - module: "ldap_auth_provider.LdapAuthProvider" config: enabled: true uri: "ldap://ldap.example.com:389" start_tls: true base: "ou=users,dc=example,dc=com" attributes: uid: "cn" mail: "email" name: "givenName" #bind_dn: #bind_password: #filter: "(objectClass=posixAccount)" Troubleshooting and Debugging ----------------------------- ``matrix-synapse-ldap3`` logging is included in the Synapse homeserver log (typically ``homeserver.log``). The LDAP plugin log level can be increased to ``DEBUG`` for troubleshooting and debugging by making the following modifications to your Synapse server's logging configuration file: - Set the value for `handlers.file.level` to `DEBUG`: .. code:: yaml handlers: file: # [...] level: DEBUG - Add the following to the `loggers` section: .. code:: yaml loggers: # [...] ldap3: level: DEBUG ldap_auth_provider: level: DEBUG Finally, restart your Synapse server for the changes to take effect: .. code:: sh synctl restart Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python :: 2.7 matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/requires.txt0000644000175000017500000000005613140173105025613 0ustar hexahexa00000000000000Twisted>=15.1.0 ldap3>=0.9.5 service_identity matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/dependency_links.txt0000644000175000017500000000000113140173105027260 0ustar hexahexa00000000000000 matrix-synapse-ldap3-0.1.3/matrix_synapse_ldap3.egg-info/SOURCES.txt0000644000175000017500000000054213140173105025077 0ustar hexahexa00000000000000LICENSE MANIFEST.in README.rst ldap_auth_provider.py requirements.txt setup.cfg setup.py tox.ini matrix_synapse_ldap3.egg-info/PKG-INFO matrix_synapse_ldap3.egg-info/SOURCES.txt matrix_synapse_ldap3.egg-info/dependency_links.txt matrix_synapse_ldap3.egg-info/requires.txt matrix_synapse_ldap3.egg-info/top_level.txt tests/__init__.py tests/test_simple.pymatrix-synapse-ldap3-0.1.3/requirements.txt0000644000175000017500000000000513017075440020654 0ustar hexahexa00000000000000-e . matrix-synapse-ldap3-0.1.3/setup.py0000755000175000017500000000346613140172154017120 0ustar hexahexa00000000000000#!/usr/bin/env python # Copyright 2016 OpenMarket Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from setuptools import setup from codecs import open import os here = os.path.abspath(os.path.dirname(__file__)) def read_file(path_segments): """Read a UTF-8 file from the package. Takes a list of strings to join to make the path""" file_path = os.path.join(here, *path_segments) with open(file_path, encoding="utf-8") as f: return f.read() def exec_file(path_segments, name): """Extract a constant from a python file by looking for a line defining the constant and executing it.""" result = {} code = read_file(path_segments) lines = [line for line in code.split('\n') if line.startswith(name)] exec("\n".join(lines), result) return result[name] setup( name="matrix-synapse-ldap3", version=exec_file(("ldap_auth_provider.py",), "__version__"), py_modules=["ldap_auth_provider"], description="An LDAP3 auth provider for Synapse", install_requires=[ "Twisted>=15.1.0", "ldap3>=0.9.5", "service_identity", ], long_description=read_file(("README.rst",)), classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2.7', ], ) matrix-synapse-ldap3-0.1.3/tests/0000755000175000017500000000000013140173105016531 5ustar hexahexa00000000000000matrix-synapse-ldap3-0.1.3/tests/test_simple.py0000644000175000017500000000460113017075440021442 0ustar hexahexa00000000000000# -*- coding: utf-8 -*- # Copyright 2016 OpenMarket Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from twisted.trial import unittest from twisted.internet import defer from mock import Mock from . import create_ldap_server, create_auth_provider import logging logging.basicConfig() class LdapSimpleTestCase(unittest.TestCase): @defer.inlineCallbacks def test_unknown_user(self): server = yield create_ldap_server() with server: account_handler = Mock(spec_set=[]) provider = create_auth_provider(server, account_handler) result = yield provider.check_password("@non_existent:test", "password") self.assertFalse(result) @defer.inlineCallbacks def test_incorrect_pwd(self): server = yield create_ldap_server() with server: account_handler = Mock(spec_set=[]) provider = create_auth_provider(server, account_handler) result = yield provider.check_password("@bob:test", "wrong_password") self.assertFalse(result) @defer.inlineCallbacks def test_correct_pwd(self): server = yield create_ldap_server() with server: account_handler = Mock(spec_set=["check_user_exists"]) account_handler.check_user_exists.return_value = True provider = create_auth_provider(server, account_handler) result = yield provider.check_password("@bob:test", "secret") self.assertTrue(result) @defer.inlineCallbacks def test_no_pwd(self): server = yield create_ldap_server() with server: account_handler = Mock(spec_set=["check_user_exists"]) account_handler.check_user_exists.return_value = True provider = create_auth_provider(server, account_handler) result = yield provider.check_password("@bob:test", "") self.assertFalse(result) matrix-synapse-ldap3-0.1.3/tests/__init__.py0000644000175000017500000000577013017075440020661 0ustar hexahexa00000000000000from twisted.internet.endpoints import serverFromString from twisted.internet.protocol import ServerFactory from twisted.internet import reactor, defer from twisted.python.components import registerAdapter from ldaptor.inmemory import fromLDIFFile from ldaptor.interfaces import IConnectedLDAPEntry from ldaptor.protocols.ldap.ldapserver import LDAPServer from cStringIO import StringIO from ldap_auth_provider import LdapAuthProvider LDIF = """\ dn: dc=org dc: org objectClass: dcObject dn: dc=example,dc=org dc: example objectClass: dcObject objectClass: organization dn: ou=people,dc=example,dc=org objectClass: organizationalUnit ou: people dn: cn=bob,ou=people,dc=example,dc=org cn: bob objectclass: person gn: bob mail: bob@example.org userPassword: secret dn: cn=jdoe,ou=people,dc=example,dc=org cn: jdoe gn: John Doe objectClass: person userPassword: terces dn: cn=jsmith,ou=people,dc=example,dc=org cn: jsmith gn: John Smith objectClass: person userPassword: eekretsay """ @defer.inlineCallbacks def _create_db(): f = StringIO(LDIF) db = yield fromLDIFFile(f) f.close() defer.returnValue(db) class _LDAPServerFactory(ServerFactory): protocol = LDAPServer def __init__(self, root): self.root = root def buildProtocol(self, addr): proto = self.protocol() proto.debug = self.debug proto.factory = self return proto class _LdapServer(object): def __init__(self, listener): self.listener = listener self._closed = False def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): self.close() def close(self): if not self._closed: self._closed = True self.listener.stopListening() # When the LDAP Server protocol wants to manipulate the DIT, it invokes # `root = interfaces.IConnectedLDAPEntry(self.factory)` to get the root # of the DIT. The factory that creates the protocol must therefore # be adapted to the IConnectedLDAPEntry interface. registerAdapter( lambda x: x.root, _LDAPServerFactory, IConnectedLDAPEntry ) @defer.inlineCallbacks def create_ldap_server(): "Returns a context manager that represents the LDAP server." db = yield _create_db() factory = _LDAPServerFactory(db) factory.debug = True # We just pick an arbitrary port to listen on. serverEndpointStr = "tcp:0" e = serverFromString(reactor, serverEndpointStr) listener = yield e.listen(factory) defer.returnValue(_LdapServer(listener)) def create_auth_provider(server, account_handler): "Creates an LdapAuthProvider from an LDAP server and a mock account_handler" config = LdapAuthProvider.parse_config({ "enabled": True, "uri": "ldap://localhost:%d" % server.listener.getHost().port, "base": "ou=people,dc=example,dc=org", "attributes": { "uid": "cn", "name": "gn", "mail": "mail", }, }) return LdapAuthProvider(config, account_handler=account_handler) matrix-synapse-ldap3-0.1.3/tox.ini0000644000175000017500000000070113017075440016706 0ustar hexahexa00000000000000[tox] envlist = packaging, pep8, py27-ldap{0,1,2} [testenv] deps = Twisted>=15.1 mock ldaptor ldap0: ldap3<1.0 ldap1: ldap3>=1.0,<2.0 ldap2: ldap3>=2.0 setenv = PYTHONDONTWRITEBYTECODE = no_byte_code PYTHONPATH = . commands = trial tests [testenv:packaging] deps = check-manifest commands = check-manifest [testenv:pep8] skip_install = True basepython = python2.7 deps = flake8 commands = flake8 .