rroemhild / flask-ldapconn Goto Github PK
View Code? Open in Web Editor NEWFlask extension providing python-ldap3 connection and ORM for accessing LDAP servers.
License: BSD 2-Clause "Simplified" License
Flask extension providing python-ldap3 connection and ORM for accessing LDAP servers.
License: BSD 2-Clause "Simplified" License
I would like to use the code in this project as a starting point for an LDAP3 ORM that does not depend necessarily on flask, that is splitting the current project into two projects ldap3_orm
and ldap3_orm_flask
.
I am also planning to implement new features, e.g. inheritance for the model.
Since those changes could not easily merged back into this project I do not plan on using githubs fork button, but just push the current state to a new repository and work from there.
Please let me know what do you think about those plans!
Added rebind() method to Connection object to rebind with a different user (thanks Lorenzo)
I wasn't able to find anything about this in the docs.
after application initialization I need access to the LDAPConn object. I can't simply import it because these are blueprints, which means that they have to be imported by the application configuration and thus can't import the application configuration without circular import problems.
The documentation considers only simple setups.
So here I am forced to use current_app to access the application. ldap = LDAPConn(current_app) doesn't work, however. It yields:
AssertionError: A setup function was called after the first request was handled. This usually indicates a bug in the application where a module was not imported and decorators or other functionality was called too late. To fix this make sure to import all your view modules, database models and everything related at a central place before the application starts serving requests.
I can't init and import, and I can't init in the authentication module itself. I CAN hack the app to store the object, but before I do this I wanted to know if there is an undocumented way to get at LDAPConn through the app (using current_app)?
Hi,
i'm getting following error when i try to query ActiveDirectory ldap. Code is working with openldap without any problem.
auth_user = LdapUser.query.filter('name:'+username).first()
return self.view_functions[rule.endpoint](**req.view_args)
File "/webapps/kpimanager/app/views.py", line 40, in login
if not User.try_login(form.username.data, form.password.data):
File "/webapps/kpimanager/app/models.py", line 42, in try_login
auth_user = LdapUser.query.filter('name:'+username).first()
File "/platform/python-2.7/lib/python2.7/site-packages/flask_ldapconn/query.py", line 70, in first
for entry in iter(self):
File "/platform/python-2.7/lib/python2.7/site-packages/flask_ldapconn/query.py", line 23, in iter
ldapentry = new_cls(dn=entry.entry_get_dn(),
AttributeError: 'NoneType' object has no attribute 'entry_get_dn'
When using
LDAP_REQUIRE_CERT = ssl.CERT_REQUIRED
LDAP_USE_SSL = True
LDAP_USE_TLS = False
LDAP_PORT = 8636
not even the hostname check is done; let alone a full certificate chain check. Also, the SSL method is not configurable (e.g. switchable to PROTOCOL_TLSv1_2), neither is e.g. the cipher string.
No tests are present for any of this :(
Hello,
A basic question; how can increase (or modify) the number of maximum results that i can get in a query?
For example, i have a basic model named group, with basic attributes cn, description and member.
I know that i have in my ldap 130 entries of that model; but if I do:
group.query.filter('cn: *').all()
Instead of get my list with the 130 entries, I only got a list with the first 50 results.
So... How I can change this? or. How I can get more results than these first 50?
So, thanks for the answers!
entry.py detects changes to an ldap entry item only if the change triggers <item>.__setattr__
. This is not the case with mutable objects like lists, they can be updates without __setattr__
being triggered. Best example is calling .append
on a list item, this will not result in an ldap change. This behaviour should be documented in the README.
Thanks for this nice and useful repo!
I've found a bunch of new deprecation warnings while running tests on our software, I guess this happened after a recent release of Flask (2.2):
tests/server/blueprints/login/test_views.py::test_ldap_login
/Users/chiararasi/miniconda3/envs/py38/lib/python3.8/site-packages/flask_ldapconn/init.py:114: DeprecationWarning: '_app_ctx_stack' is deprecated and will be removed in Flask 2.3. Use 'g' to store data, or 'app_ctx' to access the current context.
ctx = _app_ctx_stack.top
Could not find a version that satisfies the requirement ldap3==1.0.1 (from versions: 0.9.8.2.post1, 0.9.8.3.post1, 0.9.8.4, 0.9.8.5.post2, 0.9.8.6, 0.9.8.7, 0.9.8.8, 0.9.9, 0.9.9.1, 0.9.9.2, 0.9.9.3, 1.0.2)
No matching distribution found for ldap3==1.0.1
For example, i define a model named Group. I want to use groupOfNames, so i also define an attribute named member. In ldap schema this attribute has non-single value, so i want to work with them in python as with list. And when i create Group instance using flask-ldapconn i provide fake dn ( like uid=nobody,... ) via members.append() and call save() method. Works! But later, when i want to append new user, python says that member is str. Why?
Hi, it has just been published the release 2 of ldap3.
Is somebody already working on integrating it? It could be cool to use the abstraction level both for reading and writing as explained here [1]. It would make the code much simpler, but it'll required a complete rewrite.
[1] http://ldap3.readthedocs.io/tutorial_abstraction_writer.html
It would be cool to allow inheritance in the model, e.g. define a Person
class and a User
class derived from that, adding objectClasses and attributes in the latter.
I am maintaining Flask-LDAPConn in Debian and have the following remarks:
I'm trying to write binary data (python type bytes) to a jpegPhoto attribute and get errors:
class User(UserMixin, ldap.Entry):
...
jpegPhoto = ldap.Attribute('jpegPhoto')
jpeg = ... # binary data, bytes type
user.jpegPhoto = jpeg
user.save()
This results in "AttributeError: 'int' object has no attribute 'encode'" in Attribute.get_changes_tuple() because it thinks it's a multi-valued attribute of integers.
jpeg = ... # binary data, bytes type
user.jpegPhoto = [jpeg]
user.save()
A slight improvement, now the data is at least accepted as a single value, error is "AttributeError: 'bytes' object has no attribute 'encode'".
class BytesLDAPHackAdapter(bytes):
def encode(self, enc):
return self
jpeg = ... # binary data, bytes type
user.jpegPhoto = [BytesLDAPHackAdapter(jpeg)]
This work-around does the trick by providing a no-op encode member function. But it would be much better if LDAPConn would deal properly with bytes typed data. The problem is that bytes is iterable and not str as far as I can see.
Thank you for a nice library otherwise :-)
Refer to: https://github.com/rroemhild/flask-ldapconn/blob/master/flask_ldapconn.py#L116
This line forces the connection to use the simple authentication method. Why not allow the user to specify the type of authentication they want?
Personally I want to use the anonymous authentication method.
Trying to test extension, not working out of the box as expected, got error when entering intentionally wrong credentials
flask_ldapconn\__init__.py", line 162, in authenticate
username = response[0]['dn']
KeyError: 'dn'
Slight code changing required. In line 162 of __init__.py
except (LDAPInvalidDnError, LDAPInvalidFilterError, IndexError):
to empty
except:
gave correct error handling! Hope this will help to someone.
The fix/change in #24 and #25 mostly breaks working with list attributes in any sensible/easy or obviously correct way. As hinted to in the comment for b59c495 a field is either a list or it is not a list, but there's no way to indicate that in the model, and #24 makes accessing it much harder.
class Test(ldap.Entry):
object_classes = ['groupOfNames']
entry_rdn = ['cn', 'base_dn']
name = ldap.Attribute('cn')
members = ldap.Attribute('member', default=[])
object1
should have 1 member
attribute value (foo
), object2
should have 2 member
attribute values (bar
and baz
).print ", ".join(object1.members)
print ", ".join(object2.members)
foo
bar, baz
f, o, o
bar, baz
For basically all objects with multivalued attributes the accessing code does not want to care whether currently there's only one item in the list or multiple items. Example: I want to display the list with comma-separated values (", ".join(β¦)
). With the new behaviour I would have to add a case distinction (along the lines of object1.members if isinstance(object1.members, str) else ", ".join(object1.members)
).
Beginning with #24 all attribute access in LDAPEntry
is delegated to the LDAPAttribute
value
property which conditionally returns a single value or a list of values. It's now impossible to access the LDAPAttribute
values
list to work around the behaviour of the value
getter.
The two only workarounds I see:
LDAPEntry._attributes
internal property directly. (", ".join(object1._attributes["members"].values)
)get_attributes_dict()
: ", ".join(object1.get_attributes_dict()["members"])
The most preferred solution would be some way to declare an LDAPAttribute to be a list and circumvent all "now it's a list, now it's a string, you can never know" magic.
Hello Rafael,
The requirement for this library ldap3 version 0.9.7.10 is not available, please update the library so we can use it with ldap3 0.9.8.3. Thank you
Models should be writable and saved back to the server.
Allow to change the user Password by admin or user with extend.standard.modify_password()
.
The documentation says that an attribute is accessed by just, well, accessing it, but the code really returns the raw LDAPAttribute object instead of its value. It seems like a minor mistake in the code, but I wonder why noone noticed itβ¦
Allow to set more than one ldap server.
I'm able to obtain a list of Entry
objects and convert these to dictionaries for JSON serialization using get_attributes_dict()
.
The result is a list of objects containing the expected properties - however, values are contained in lists; Is there an easy way to get "single value" properties as string instead?
BTW - I noticed LDAPAttribute
is missing some features available in AttrDef
, such as post_query
. Any plans on implementing those?
Anyway - thank you for a great extension.
Have a good day
Hi
I can seem to set the LDAP_SERVER Variable
I get this error
ldap3.core.exceptions.LDAPSocketOpenError: ('unable to open socket', [(LDAPSocketOpenError('socket connection error while opening: [Errno 111] Connection refused',), ('::1', 389, 0, 0)), (LDAPSocketOpenError('socket connection error while opening: [Errno 111] Connection refused',), ('127.0.0.1', 389))])
I have set this in bot the app.py and config.py
LDAP_SERVER = 'mmcenvdc01.metmom.mmih.biz'
I also tried
app.config['LDAP_SERVER'] = 'mmcenvdc01.metmom.mmih.biz'
My use case is basically that a user logs into (using authenticate()) the app and changes his personal data (e.g. mail). The server is configured that you can query a user without binding using a password, so I do not need to store the admin password in a plain text file and a user binding with his own password may only ever change attributes he is entitled to.
But to change a users data, I would need his connection (as in authenticate()) instead of the general connection of the app (as configured in the plain text config). Given that, I think I would need to replace the original connection somehow and maybe even use the LDAPEntry object for flask-login (that also does not work, at least in the obvious way). Or am I missing something?
Is what I describe here possible to achieve with flask-ldapconn? If so, how?
Using python 3.7.0,
On the last version of flask_ldapconn (0.10.0)
when you try to serialize as json an object created from a ldap Model, throws this exception:
TypeError: Object of type LDAPAttribute is not JSON serializable
In flask_ldapconn 0.7.0 this didn't happen and works perfectli. what changed on these versions?
this is the model:
class User(LDAPEntry):
base_dn = settings.config[config_name].LDAP_BASE_DN
object_classes = ['posixAccount']
name = Attribute('cn')
email = Attribute('mail')
userid = Attribute('uid')
surname = Attribute('sn')
givenname = Attribute('givenName')
manager = Attribute('manager')
groups = Attribute('memberOf')
phone = Attribute('mobile')
organization = Attribute('o')
organizationUnit = Attribute('ou')
def to_json(self):
return jsonify(
dn=self._dn,
username=self.userid,
firstName=self.givenname,
lastName=self.surname,
email=self.email,
name=self.name,
phone=self.phone,
manager=self.manager,
ldapGroups=self.groups,
company=self.organization,
unit=self.organizationUnit,
)
In 0.7.0 you can call toJson and works perfectly!
but with 0.10.0 it completely fails.
how to fix that?
For the configuration, I used the default values.
Thanks!
Hi!
In my project, I needed to fetch metrics about LDAP connection, like amount of queries, etc. ldap3 has a usage class that allows it, but the flask-ldapconn by default doesn't allow changing this. I 'solved' this by hacking the LDAPConn object, but it'd be nice to have it in the upstream package by default.
I made a commit in my fork here - matejpipiska@fd05b04
But I am not sure how to write a test for this.
LDAPConn.connection.usage gives output like this:
Connection Usage:
Time: [elapsed: 0:03:02.554842]
Initial start time: 2018-08-13T11:28:32.031308
Open socket time: 2018-08-13T11:28:32.031308
Last transmitted time: 2018-08-13T11:31:34.244952
Last received time: 2018-08-13T11:31:34.275247
Close socket time:
Server:
Servers from pool: 0
Sockets open: 1
Sockets closed: 0
Sockets wrapped: 0
Bytes: 17738951
Transmitted: 2690634
Received: 15048317
Messages: 12730
Transmitted: 4244
Received: 8486
Operations: 4244
Abandon: 0
Bind: 2
Add: 0
Compare: 0
Delete: 0
Extended: 0
Modify: 0
ModifyDn: 0
Search: 4242
Unbind: 0
Referrals:
Received: 0
Followed: 0
Connections: 0
Restartable tries: 0
Failed restarts: 0
Successful restarts: 0
Going to a specific metric, like:
LDAPConn.connection.usage.search_operations gives 4242
Can someone help me with a test for this?
Thanks :)
For caching operation (for example flask-cache), is not present pickle works
I have model - app.models.Contact
In [14]: contact.__class__.name
Out[14]: <flask_ldapconn.attribute.LDAPAttribute at 0x7fe9c56cb358>
In [15]: pickle.dumps(contact)
---------------------------------------------------------------------------
PicklingError Traceback (most recent call last)
<ipython-input-15-348ac8e78f5a> in <module>()
----> 1 pickle.dumps(contact)
PicklingError: Can't pickle <class 'flask_ldapconn.entry.Contact'>: attribute lookup Contact on flask_ldapconn.entry failed
Hi,
I have tried your example given here But it seems to raises an exception because LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1_2 is used. I'm trying to use TLS1.2 over port 389.
This was working it seems when I used the following package versions:
Flask==0.10.1
Flask-LDAPConn==0.6.12
ldap3==1.2.2
Now I have updated to the following versions and it no longer works:
Flask==0.12
Flask-LDAPConn==0.6.13
ldap3==1.3.1
Here is the code I was using which is based directly on your example.
import ssl
from flask import Flask, current_app
from flask_ldapconn import LDAPConn
from ldap3 import SUBTREE
LDAP_SERVER = '<DCName>' # Domain Controller machine name
LDAP_BASEDN = 'DC=<mycompany>,DC=<loca>l'
LDAP_BINDDN = '<username>' # Username
LDAP_SECRET = "<password>" # Password
LDAP_PORT = 389
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
SECRET_KEY = "shhh... it's a secret!"
HTTP_ADDR = 'localhost'
HTTP_PORT = 8000
DEBUG = True
app = Flask(__name__)
@app.route('/')
def index():
try:
ldapc = current_app.ldap.connection
search_filter = '(&(samaccounttype=805306368) (samaccountname=%s))'
attributes = ['sn', 'givenName', 'uid', 'mail']
ldapc.search(current_app.config["LDAP_BASEDN"], search_filter, SUBTREE,
attributes=attributes)
response = ldapc.response
except Exception as ex:
raise
def main():
app.config.from_pyfile(__file__)
app.ldap = LDAPConn(app)
app.run(app.config["HTTP_ADDR"], app.config["HTTP_PORT"], debug=True)
if __name__ == '__main__':
main()
When you access the main page from a browser the following exception is thrown:
ldap3.core.exceptions.LDAPStartTLSError: ('wrap socket error: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:645)',)
Traceback (most recent call last):
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "D:\venv\testserver\lib\site-packages\flask\_compat.py", line 33, in reraise
raise value
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "D:\venv\testserver\lib\site-packages\flask\_compat.py", line 33, in reraise
raise value
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "D:\venv\testserver\lib\site-packages\flask\app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "D:\testserver\ldaptest.py", line 30, in index
CMD_ADD_EXCEPTION_BREAK, CMD_SMART_STEP_INTO, InternalConsoleExec, NetCommandFactory, \
File "D:\venv\testserver\lib\site-packages\flask_ldapconn\__init__.py", line 119, in connection
current_app.config['LDAP_SECRET']
File "D:\venv\testserver\lib\site-packages\flask_ldapconn\__init__.py", line 96, in connect
read_only=current_app.config['LDAP_READ_ONLY'],
File "D:\venv\testserver\lib\site-packages\ldap3\core\connection.py", line 293, in __init__
self.start_tls(read_server_info=False)
File "D:\venv\testserver\lib\site-packages\ldap3\core\connection.py", line 1040, in start_tls
if self.server.tls.start_tls(self) and self.strategy.sync: # for async connections _start_tls is run by the strategy
File "D:\venv\testserver\lib\site-packages\ldap3\core\tls.py", line 237, in start_tls
return self._start_tls(connection)
File "D:\venv\testserver\lib\site-packages\ldap3\core\tls.py", line 252, in _start_tls
raise start_tls_exception_factory(LDAPStartTLSError, exc)(connection.last_error)
If I use the default of LDAP_TLS_VERSION = ssl.PROTOCOL_TLSv1 then it works but wanted to use TLS1.2.
Any ideas what this exception really means or if the code I'm now using is wrong for this version of Flask-LDAPConn?
Cheers,
Del
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.