pax_global_header00006660000000000000000000000064136001777170014523gustar00rootroot0000000000000052 comment=c823a99defdea141654012885a138cc98fd23c9a flask-ldapconn-0.10.1/000077500000000000000000000000001360017771700144765ustar00rootroot00000000000000flask-ldapconn-0.10.1/.gitignore000066400000000000000000000004201360017771700164620ustar00rootroot00000000000000config.py *.py[co] __pycache__/ # Packages *.egg *.egg-info dist *build eggs parts bin var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox htmlcov .cache #Translations *.mo #Mr Developer .mr.developer.cfg flask-ldapconn-0.10.1/.travis.yml000066400000000000000000000005131360017771700166060ustar00rootroot00000000000000language: python sudo: required dist: xenial services: - docker before_install: - docker pull rroemhild/test-openldap - docker run -d --privileged -p 127.0.0.1:389:389 -p 127.0.0.1:636:636 rroemhild/test-openldap python: - "3.5" - "3.6" - "3.7" install: - pip install pipenv - pipenv install --dev script: py.test flask-ldapconn-0.10.1/CHANGELOG.rst000066400000000000000000000066561360017771700165340ustar00rootroot00000000000000Changelog ========= 0.10.1 (2010-12-23) ------------------- * Fix security issue: allows authentication without password (Roland) 0.10.0 (2019-10-20) ------------------- * End support for Python 2.7 * fix adding zero integer attribute value (HAMANO Tsukasa) 0.9.0 (2019-08-17) ------------------ * Fix anonymous binding where no security layer is need at all (Matthias Tafelmeier @cherusk) 0.8.0 (2019-05-09) ------------------ * Refactored LDAPAttribute class (Alexei Margasov @alexei38) * Add support for Python 3.7 * End support for Python 3.4 * Update requirements in Pipfile.lock 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.10.1/LICENSE000066400000000000000000000025161360017771700155070ustar00rootroot00000000000000Copyright (c) 2017-2019, 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.10.1/MANIFEST.in000066400000000000000000000000511360017771700162300ustar00rootroot00000000000000include README.rst CHANGELOG.rst LICENSE flask-ldapconn-0.10.1/Pipfile000066400000000000000000000003261360017771700160120ustar00rootroot00000000000000[[source]] url = "https://pypi.python.org/simple" verify_ssl = true name = "pypi" [packages] ldap3 = ">=2.3" six = ">=1.10" Flask = ">=0.12" [dev-packages] docker = "*" pytest = "*" coverage = "*" pathlib2 = "*" flask-ldapconn-0.10.1/Pipfile.lock000066400000000000000000000327401360017771700167460ustar00rootroot00000000000000{ "_meta": { "hash": { "sha256": "6c85c45a9fea24d5b3f1b1d1f51ccdcfd982ac425dcba5e2acf80e550cb1c0aa" }, "pipfile-spec": 6, "requires": {}, "sources": [ { "name": "pypi", "url": "https://pypi.python.org/simple", "verify_ssl": true } ] }, "default": { "click": { "hashes": [ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], "version": "==7.0" }, "flask": { "hashes": [ "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" ], "index": "pypi", "version": "==1.0.2" }, "itsdangerous": { "hashes": [ "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], "version": "==1.1.0" }, "jinja2": { "hashes": [ "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" ], "version": "==2.10.3" }, "ldap3": { "hashes": [ "sha256:0533eefc18d5ce0532f07fe1ce4e1f27128e1073845a6bf39fef2c828a401bb8", "sha256:a92e380a0265963dc5507580ecd51eb84677e00c3d7800517b6442ef046f6ece" ], "index": "pypi", "version": "==2.6" }, "markupsafe": { "hashes": [ "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], "version": "==1.1.1" }, "pyasn1": { "hashes": [ "sha256:62cdade8b5530f0b185e09855dd422bc05c0bbff6b72ff61381c09dac7befd8c", "sha256:a9495356ca1d66ed197a0f72b41eb1823cf7ea8b5bd07191673e8147aecf8604" ], "version": "==0.4.7" }, "six": { "hashes": [ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], "index": "pypi", "version": "==1.12.0" }, "werkzeug": { "hashes": [ "sha256:97660b282aa7e29f94f3fe378e5c7162d7ab9d601a8dbb1cbb2ffc8f0e54607d", "sha256:cfd1281b1748288e59762c0e174d64d8bcb2b70e7c57bc4a1203c8825af24ac3" ], "index": "pypi", "version": "==0.15.3" } }, "develop": { "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" ], "version": "==1.3.0" }, "attrs": { "hashes": [ "sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2", "sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396" ], "version": "==19.2.0" }, "certifi": { "hashes": [ "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], "version": "==2019.9.11" }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" ], "version": "==3.0.4" }, "coverage": { "hashes": [ "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" ], "index": "pypi", "version": "==4.5.3" }, "docker": { "hashes": [ "sha256:2b1f48041cfdcc9f6b5da0e04e0e326ded225e736762ade2060000e708f4c9b7", "sha256:c456ded5420af5860441219ff8e51cdec531d65f4a9e948ccd4133e063b72f50" ], "index": "pypi", "version": "==3.7.2" }, "docker-pycreds": { "hashes": [ "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49" ], "version": "==0.4.0" }, "idna": { "hashes": [ "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], "version": "==2.8" }, "importlib-metadata": { "hashes": [ "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" ], "markers": "python_version < '3.8'", "version": "==0.23" }, "more-itertools": { "hashes": [ "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" ], "markers": "python_version > '2.7'", "version": "==7.2.0" }, "pathlib2": { "hashes": [ "sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742", "sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7" ], "index": "pypi", "version": "==2.3.3" }, "pluggy": { "hashes": [ "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" ], "version": "==0.13.0" }, "py": { "hashes": [ "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], "version": "==1.8.0" }, "pytest": { "hashes": [ "sha256:136632a40451162cdfc18fe4d7ecc5d169b558a3d4bbb1603d4005308a42fd03", "sha256:62b129bf8368554ca7a942cbdb57ea26aafef46cc65bc317cdac3967e54483a3" ], "index": "pypi", "version": "==4.4.2" }, "requests": { "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], "version": "==2.22.0" }, "six": { "hashes": [ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], "index": "pypi", "version": "==1.12.0" }, "urllib3": { "hashes": [ "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" ], "version": "==1.25.6" }, "websocket-client": { "hashes": [ "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" ], "version": "==0.56.0" }, "zipp": { "hashes": [ "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" ], "version": "==0.6.0" } } } flask-ldapconn-0.10.1/README.rst000066400000000000000000000132111360017771700161630ustar00rootroot00000000000000Flask-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.10.1/flask_ldapconn/000077500000000000000000000000001360017771700174545ustar00rootroot00000000000000flask-ldapconn-0.10.1/flask_ldapconn/__init__.py000066400000000000000000000156141360017771700215740ustar00rootroot00000000000000# -*- 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_NONE, AUTO_BIND_NO_TLS, AUTO_BIND_TLS_BEFORE_BIND from ldap3 import ANONYMOUS, SIMPLE, SASL from ldap3.core.exceptions import (LDAPBindError, LDAPInvalidFilterError, LDAPInvalidDnError) from ldap3.utils.dn import parse_dn from .entry import LDAPEntry from .attribute import LdapField __all__ = ('LDAPConn',) class LDAPConn(object): def __init__(self, app=None): self.Entry = LDAPEntry self.Attribute = LdapField 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, anonymous=False): auto_bind_strategy = AUTO_BIND_TLS_BEFORE_BIND authentication_policy = SIMPLE if current_app.config['LDAP_USE_TLS'] is not True: auto_bind_strategy = AUTO_BIND_NO_TLS if anonymous: authentication_policy = ANONYMOUS user = None password = None 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'], authentication=authentication_policy, 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'], anonymous=None in [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.10.1/flask_ldapconn/attribute.py000066400000000000000000000055711360017771700220410ustar00rootroot00000000000000# -*- 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 LdapField(object): def __init__(self, name, validate=None, default=None, dereference_dn=None): self.name = name self.validate = validate self.default = default self.dereference_dn = None 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) class LDAPAttribute(object): def __init__(self, name): self.__dict__['name'] = name self.__dict__['values'] = [] self.__dict__['changetype'] = None 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 = [] flask-ldapconn-0.10.1/flask_ldapconn/entry.py000066400000000000000000000145761360017771700212040ustar00rootroot00000000000000# -*- coding: utf-8 -*- import json from flask import current_app from six import add_metaclass from ldap3.utils.dn import safe_dn from ldap3.utils.conv import check_json_dict, format_json from ldap3.core.exceptions import LDAPAttributeError from .query import BaseQuery from .attribute import LDAPAttribute, LdapField __all__ = ('LDAPEntry',) class LDAPEntryMeta(type): def __init__(cls, name, bases, attr): cls._fields = {} for key, value in attr.items(): if isinstance(value, LdapField): cls._fields[key] = value for base in bases: if isinstance(base, LDAPEntryMeta): cls._fields.update(base._fields) # Deduplicate object classes cls.object_classes = list( set(cls.object_classes + base.object_classes)) @property def query(cls): return BaseQuery(cls) @add_metaclass(LDAPEntryMeta) class LDAPEntry(object): base_dn = None entry_rdn = ['cn'] object_classes = ['top'] sub_tree = True operational_attributes = False _changetype = 'add' def __init__(self, dn=None, changetype='add', **kwargs): self._attributes = {} self._dn = dn self._changetype = changetype if kwargs: for key, value in kwargs.items(): self._store_attr(key, value, init=True) for key, ldap_attr in self._fields.items(): if not self._isstored(key): self._store_attr(key, []) @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 key, attr in self._attributes.items(): if attr.name in self.entry_rdn: if len(self._attributes[key]) == 1: rdn = '{attr}={value}'.format( attr=attr.name, value=self._attributes[key].value ) rdn_list.append(rdn) dn = '{rdn},{base_dn}'.format(rdn='+'.join(rdn_list), base_dn=self.base_dn) self._dn = safe_dn(dn) @classmethod def _get_field(cls, attr): return cls._fields.get(attr) @classmethod def _get_field_name(cls, attr): if cls._get_field(attr): return cls._get_field(attr).name def _store_attr(self, attr, value=[], init=False): if not self._get_field(attr): raise LDAPAttributeError('attribute not found') if value is None: value = [] if not self._attributes.get(attr): self._attributes[attr] = LDAPAttribute(self._get_field_name(attr)) self._attributes[attr].value = value if init: self._attributes[attr].__dict__['changetype'] = None def _isstored(self, attr): return self._attributes.get(attr) def _get_attr(self, attr): if self._isstored(attr): return self._attributes[attr].value return None def __getattribute__(self, item): if item != '_fields' and item in self._fields: return self._get_attr(item) return super(LDAPModel, self).__getattribute__(item) def __setattr__(self, key, value): if key != '_fields' and key in self._fields: self._store_attr(key, value) else: return super(LDAPModel, self).__setattr__(key, value) 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 != []: add_dict.update({self._get_field_name(attribute_key): 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: changes = self._attributes[attribute_key].get_changes_tuple() modify_dict.update({self._get_field_name(attribute_key): changes}) return modify_dict @property def connection(self): return current_app.extensions.get('ldap_conn') def delete(self): '''Delete this entry from LDAP server''' return self.connection.connection.delete(self.dn) def save(self): '''Save the current instance''' attrs = self.get_attributes_dict() if self._changetype == 'add': changes = self.get_entry_add_dict(attrs) return self.connection.connection.add(self.dn, self.object_classes, changes) elif self._changetype == 'modify': changes = self.get_entry_modify_dict(attrs) 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.10.1/flask_ldapconn/query.py000066400000000000000000000050661360017771700212020ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys from flask import current_app from ldap3 import BASE, Reader, SUBTREE, ObjectDef __all__ = ('BaseQuery',) class BaseQuery(object): def __init__(self, obj): self.obj = obj self.query = [] self.base_dn = obj.base_dn self.sub_tree = obj.sub_tree self.object_def = ObjectDef(obj.object_classes) self.operational_attributes = obj.operational_attributes self.components_in_and = True def add_abstract_attr_def(self): for name, attr in self.obj._fields.items(): attr_def = attr.get_abstract_attr_def(name) self.object_def.add_attribute(attr_def) def __iter__(self): for entry in self.get_reader_result(): module = sys.modules.get(self.obj.__module__) new_cls = getattr(module, self.obj.__name__) 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') self.add_abstract_attr_def() 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.10.1/requirements.txt000066400000000000000000000000411360017771700177550ustar00rootroot00000000000000six>=1.10 Flask>=0.12 ldap3>=2.3 flask-ldapconn-0.10.1/setup.py000066400000000000000000000023761360017771700162200ustar00rootroot00000000000000# -*- 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.10.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 :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Framework :: Flask', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], ) flask-ldapconn-0.10.1/test_flask_ldapconn.py000066400000000000000000000503471360017771700210760ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import ssl import json import time import random import string import unittest import flask from ldap3 import SUBTREE, STRING_TYPES from ldap3.core.exceptions import LDAPAttributeError, LDAPStartTLSError from flask_ldapconn import LDAPConn from flask_ldapconn.entry import LDAPEntry from flask_ldapconn.attribute import LdapField TESTING = True USER_EMAIL = 'fry@planetexpress.com' USER_PASSWORD = 'fry' LDAP_SERVER = 'localhost' LDAP_BINDDN = 'cn=admin,dc=planetexpress,dc=com' LDAP_SECRET = 'GoodNewsEveryone' LDAP_BASEDN = 'dc=planetexpress,dc=com' LDAP_SEARCH_ATTR = 'mail' LDAP_SEARCH_FILTER = '(mail=%s)' % USER_EMAIL LDAP_QUERY_FILTER = 'email: %s' % USER_EMAIL LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1 LDAP_REQUIRE_CERT = ssl.CERT_NONE LDAP_AUTH_BASEDN = 'ou=people,dc=planetexpress,dc=com' LDAP_AUTH_ATTR = 'mail' LDAP_AUTH_SEARCH_FILTER = '(objectClass=inetOrgPerson)' UID_SUFFIX = ''.join(random.choice( string.ascii_lowercase + string.digits ) for _ in range(6)) def is_json(myjson): try: json.loads(myjson) except (ValueError, TypeError): return False return True class User(LDAPEntry): # LDAP meta-data base_dn = LDAP_AUTH_BASEDN entry_rdn = ['cn', 'uid'] object_classes = ['inetOrgPerson'] # inetOrgPerson name = LdapField('cn') email = LdapField('mail') title = LdapField('title') userid = LdapField('uid') surname = LdapField('sn') givenname = LdapField('givenName') class Account(User): # LDAP meta-data object_classes = ['posixAccount'] # posixAccount uidnumber = LdapField('uidNumber') gidnumber = LdapField('gidNumber') shell = LdapField('loginShell') home = LdapField('homeDirectory') password = LdapField('userPassword') class LDAPConnTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) ldap = LDAPConn(app) self.app = app self.ldap = ldap class LDAPConnSearchTestCase(LDAPConnTestCase): def test_connection_search(self): attr = self.app.config['LDAP_SEARCH_ATTR'] with self.app.test_request_context(): ldapc = self.ldap.connection ldapc.search(self.app.config['LDAP_BASEDN'], self.app.config['LDAP_SEARCH_FILTER'], SUBTREE, attributes=[attr]) result = ldapc.result response = ldapc.response self.assertTrue(response) self.assertEqual(response[0]['attributes'][attr][0], self.app.config['USER_EMAIL']) def test_whoami(self): with self.app.test_request_context(): conn = self.ldap.connection self.assertEqual(conn.extend.standard.who_am_i(), 'dn:{}'.format(self.app.config['LDAP_BINDDN'])) class LDAPConnModelTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) ldap = LDAPConn(app) self.app = app self.ldap = ldap self.user = User def test_model_search(self): with self.app.test_request_context(): entry = self.user.query.filter( 'email: %s' % self.app.config['USER_EMAIL'] ).first() self.assertEqual(entry.email, self.app.config['USER_EMAIL']) def test_model_search_set_attribute(self): new_email = 'philip@planetexpress.com' with self.app.test_request_context(): entry = self.user.query.filter( 'email: %s' % self.app.config['USER_EMAIL'] ).first() entry.email = new_email self.assertEqual(entry.email, new_email) def test_model_search_set_attribute_list(self): new_email_list = ['philip@planetexpress.com', 'a.fry@planetexpress.com'] with self.app.test_request_context(): entry = self.user.query.filter( 'email: %s' % self.app.config['USER_EMAIL'] ).first() entry.email = new_email_list self.assertEqual(entry.email, new_email_list) def test_model_search_set_undefined_attr(self): def new_model(): user = self.user(active='1') with self.app.test_request_context(): self.assertRaises(LDAPAttributeError, new_model) def test_model_new(self): with self.app.test_request_context(): user = self.user(name='Rafael Römhild', email='rafael@planetexpress.com') self.assertEqual(user.email, 'rafael@planetexpress.com') def test_model_fetch_entry(self): uid = 'bender' with self.app.test_request_context(): user = self.user.query.filter('userid: {}'.format(uid)).first() self.assertEqual(user.userid, uid) def test_model_fetch_entry_with_components_in_and_false(self): uid = 'bender' with self.app.test_request_context(): user = self.user.query.filter( 'email: {0}, userid: {0}'.format(uid) ).all(components_in_and=False) self.assertEqual(user[0].userid, uid) def test_model_fetch_entry_authenticate(self): uid = 'fry' with self.app.test_request_context(): user = self.user.query.filter('userid: {}'.format(uid)).first() password = self.app.config['USER_PASSWORD'] self.assertTrue(user.authenticate(password)) def test_model_fetch_entry_exception(self): uid = 'xyz' with self.app.test_request_context(): user = self.user.query.filter('userid: {}'.format(uid)).first() self.assertEqual(user, None) def test_model_fetch_multible_entries(self): expected_uids = ['bender', 'fry', 'hermes', 'leela', 'professor', 'zoidberg'] response_uids = [] query_filter = 'email: *@planetexpress.com' with self.app.test_request_context(): entries = self.user.query.filter(query_filter).all() for entry in entries: response_uids.append(entry.userid) matched_uids = set(expected_uids).intersection(response_uids) self.assertEqual(len(expected_uids), len(matched_uids)) def test_model_get_dn(self): dn = 'cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com' with self.app.test_request_context(): user = self.user.query.get(dn) self.assertEqual(dn, user.dn) def test_model_get_multivalued_rdn(self): dn = 'cn=Amy Wong+sn=Kroker,ou=people,dc=planetexpress,dc=com' with self.app.test_request_context(): user = self.user.query.get(dn) self.assertEqual('Kroker', user.surname) def test_model_get_attributes_dict(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() attrs = ['name', 'email', 'userid'] attr_dict = user.get_attributes_dict() self.assertTrue(isinstance(attr_dict, dict)) for attr in attrs: self.assertTrue(isinstance(attr_dict[attr], list)) def test_model_to_json(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() self.assertTrue(is_json(user.to_json())) def test_model_to_json_str_values(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() self.assertTrue(is_json(user.to_json(str_values=True))) def test_model_iter(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() for name, field in user._fields.items(): self.assertTrue(isinstance(field, self.ldap.Attribute)) def test_model_contains(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() self.assertTrue(hasattr(user, 'userid')) def test_model_getarr_att_not_found(self): with self.app.test_request_context(): user = self.user.query.filter('userid: bender').first() self.assertFalse(hasattr(user, 'active')) def test_model_setattr(self): with self.app.test_request_context(): user = self.user.query.filter('userid: fry').first() user.userid = 'xyz' self.assertEqual(user.userid, 'xyz') def test_model_attribute_str(self): with self.app.test_request_context(): user = self.user.query.filter('userid: fry').first() self.assertTrue(isinstance(user.userid, STRING_TYPES)) def test_model_attribute_value_force_list(self): with self.app.test_request_context(): self.app.config['FORCE_ATTRIBUTE_VALUE_AS_LIST'] = True user = self.user.query.filter('userid: fry').first() self.assertTrue(isinstance(user.userid, list)) def test_model_attribute_iter(self): with self.app.test_request_context(): user = self.user.query.filter('userid: professor').first() self.assertTrue(isinstance(user.email, list)) for mail in user.email: pass def test_model_operation_add(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): new_user = self.user(name='Rafael Römhild', userid=uid, email='rafael@planetexpress.com', surname='Römhild', givenname='Raphael') self.assertTrue(new_user.save()) user = self.user.query.filter(query_filter).first() self.assertEqual(new_user.userid, user.userid) def test_model_operation_modify(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): mod_user = self.user.query.filter(query_filter).first() mod_user.givenname = 'Rafael' mod_user.title = 'SysAdmin' mod_user.email = [mod_user.email, 'it@planetexpress.co'] self.assertTrue(mod_user.save()) user = self.user.query.filter(query_filter).first() self.assertEqual(user.givenname, 'Rafael') self.assertEqual(user.surname, u'Römhild') self.assertEqual(user.title, 'SysAdmin') self.assertTrue('it@planetexpress.co' in user.email) def test_model_operation_remove(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): user = self.user.query.filter(query_filter).first() self.assertTrue(user.delete()) user = self.user.query.filter(query_filter).first() self.assertEqual(user, None) class LDAPConnModelInheritanceTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) ldap = LDAPConn(app) self.app = app self.ldap = ldap self.user = Account def test_model_operation_add(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): new_user = self.user(name='Rafael Römhild', userid=uid, email='rafael@planetexpress.com', surname='Römhild', givenname='Raphael', uidnumber=1000, gidnumber=1000, shell='/bin/false', home='/home/' + uid) self.assertTrue(new_user.save()) user = self.user.query.filter(query_filter).first() self.assertEqual(new_user.userid, user.userid) def test_model_operation_modify(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): mod_user = self.user.query.filter(query_filter).first() mod_user.givenname = 'Rafael' mod_user.title = 'SysAdmin' mod_user.shell = '/bin/bash' mod_user.email = [mod_user.email, 'it@planetexpress.co'] self.assertTrue(mod_user.save()) user = self.user.query.filter(query_filter).first() self.assertEqual(user.givenname, 'Rafael') self.assertEqual(user.surname, u'Römhild') self.assertEqual(user.title, 'SysAdmin') self.assertTrue('it@planetexpress.co' in user.email) def test_model_operation_remove(self): uid = 'rafael-{}'.format(UID_SUFFIX) query_filter = 'userid: {}'.format(uid) with self.app.test_request_context(): user = self.user.query.filter(query_filter).first() self.assertTrue(user.delete()) user = self.user.query.filter(query_filter).first() self.assertEqual(user, None) class LDAPConnAuthTestCase(LDAPConnTestCase): def test_authenticate_user(self): with self.app.test_request_context(): retval = self.ldap.authenticate( username=self.app.config['USER_EMAIL'], password=self.app.config['USER_PASSWORD'], base_dn=self.app.config['LDAP_AUTH_BASEDN'], attribute=self.app.config['LDAP_SEARCH_ATTR'], ) self.assertTrue(retval) def test_authenticate_user_with_dn(self): dn = 'cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com' with self.app.test_request_context(): retval = self.ldap.authenticate( username=dn, password=self.app.config['USER_PASSWORD'], ) self.assertTrue(retval) def test_authenticate_user_basedn_filter(self): with self.app.test_request_context(): retval = self.ldap.authenticate( username=self.app.config['USER_EMAIL'], password=self.app.config['USER_PASSWORD'], attribute=self.app.config['LDAP_SEARCH_ATTR'], base_dn=self.app.config['LDAP_AUTH_BASEDN'], search_filter=self.app.config['LDAP_AUTH_SEARCH_FILTER'] ) self.assertTrue(retval) def test_authenticate_user_invalid_credentials(self): with self.app.test_request_context(): retval = self.ldap.authenticate( username=self.app.config['USER_EMAIL'], password='testpass', attribute=self.app.config['LDAP_SEARCH_ATTR'], base_dn=self.app.config['LDAP_AUTH_BASEDN'], ) self.assertFalse(retval) def test_authenticate_user_invalid_search_filter(self): with self.app.test_request_context(): retval = self.ldap.authenticate( username=self.app.config['USER_EMAIL'], password=self.app.config['USER_PASSWORD'], attribute=self.app.config['LDAP_SEARCH_ATTR'], base_dn=self.app.config['LDAP_AUTH_BASEDN'], search_filter='x=y' ) self.assertFalse(retval) def test_authenticate_user_search_filter_no_result(self): with self.app.test_request_context(): retval = self.ldap.authenticate( username=self.app.config['USER_EMAIL'], password=self.app.config['USER_PASSWORD'], attribute=self.app.config['LDAP_SEARCH_ATTR'], base_dn=self.app.config['LDAP_AUTH_BASEDN'], search_filter='(uidNumber=*)' ) self.assertFalse(retval) class LDAPConnSSLTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) app.config['LDAP_PORT'] = app.config.get('LDAP_SSL_PORT', 636) app.config['LDAP_USE_SSL'] = True ldap = LDAPConn(app) self.app = app self.ldap = ldap def test_whoami(self): with self.app.test_request_context(): conn = self.ldap.connection self.assertEqual(conn.extend.standard.who_am_i(), 'dn:{}'.format(self.app.config['LDAP_BINDDN'])) class LDAPConnAnonymousTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) app.config['LDAP_BINDDN'] = None app.config['LDAP_SECRET'] = None ldap = LDAPConn(app) self.app = app self.ldap = ldap def test_whoami(self): with self.app.test_request_context(): conn = self.ldap.connection self.assertEqual(conn.extend.standard.who_am_i(), None) class LDAPConnTLSCertRequiredTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) app.config['LDAP_BINDDN'] = None app.config['LDAP_SECRET'] = None app.config['LDAP_REQUIRE_CERT'] = ssl.CERT_REQUIRED ldap = LDAPConn(app) self.app = app self.ldap = ldap def connect(self): return self.ldap.connection def test_connection(self): with self.app.test_request_context(): self.assertRaises(LDAPStartTLSError, self.connect) class LDAPConnNoTLSAnonymousTestCase(unittest.TestCase): def setUp(self): app = flask.Flask(__name__) app.config.from_object(__name__) app.config.from_envvar('LDAP_SETTINGS', silent=True) app.config['LDAP_BINDDN'] = None app.config['LDAP_SECRET'] = None app.config['LDAP_USE_TLS'] = False ldap = LDAPConn(app) self.app = app self.ldap = ldap def test_whoami(self): with self.app.test_request_context(): conn = self.ldap.connection self.assertEqual(conn.extend.standard.who_am_i(), None) class LDAPConnDeprecatedTestCase(LDAPConnTestCase): def test_connection_search(self): attr = self.app.config['LDAP_SEARCH_ATTR'] with self.app.test_request_context(): self.ldap.search(self.app.config['LDAP_BASEDN'], self.app.config['LDAP_SEARCH_FILTER'], SUBTREE, attributes=[attr]) result = self.ldap.result() response = self.ldap.response() self.assertTrue(response) self.assertEqual(response[0]['attributes'][attr][0], self.app.config['USER_EMAIL']) def test_whoami_deprecated(self): with self.app.test_request_context(): whoami = self.ldap.whoami() self.assertEqual(whoami, 'dn:{}'.format(self.app.config['LDAP_BINDDN'])) if __name__ == '__main__': success = False try: import docker client = docker.from_env() container = client.containers.run('rroemhild/test-openldap', ports={'389/tcp': 389, '636/tcp': 636}, detach=True, privileged=True, remove=True) print('Docker container {0} started'.format(container.id)) print('Wait 3 seconds until slapd is started...') time.sleep(3) print('Run unit test...') runner = unittest.main(exit=False) success = runner.result.wasSuccessful() print('Stop and removing container...') container.stop() except (ImportError, ValueError): print('Can\'t run docker image. Try to run tests without.') unittest.main() if success is not True: sys.exit(1)