Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error in bind_user method #44

Closed
rfaoro-gpsw opened this issue Dec 5, 2017 · 13 comments
Closed

Error in bind_user method #44

rfaoro-gpsw opened this issue Dec 5, 2017 · 13 comments

Comments

@rfaoro-gpsw
Copy link

I'm on Python 3.6.3 and I get this exception when I hit line 153 in __init__.py (https://github.com/admiralobvious/flask-simpleldap/blob/master/flask_simpleldap/__init__.py#L153):

AttributeError: 'str' object has no attribute 'decode'

I changed it back to a recent commit you had in v1.1.2, which fixed the issue:
conn.simple_bind_s(user_dn, password)
(instead of conn.simple_bind_s(user_dn.decode('utf-8'), password))

Can we get that fix back?

@alexferl
Copy link
Owner

alexferl commented Dec 6, 2017

I can't just do that, because it will break for someone else. The current code actually works for me on Python 3.6.3 as well. I am trying to figure out why it doesn't work for some people. Tell me, are you using OpenLDAP as your LDAP server?

@rfaoro-gpsw
Copy link
Author

Running an Active Directory server.
user_dn is returned as a string.

@rfaoro-gpsw
Copy link
Author

Can we add this?

if type(user_dn) == str:
    conn.simple_bind_s(user_dn, password)
else:
    conn.simple_bind_s(user_dn.decode('utf-8'), password)

@pschwede
Copy link

pschwede commented Dec 11, 2017

Or if you're just dealing with py3 compatibility:

if sys.version_info < (3,):
    conn.simple_bind_s(user_dn, password)
else:
    conn.simple_bind_s(user_dn.decode('utf-8'), password)

@rfaoro-gpsw how about you create a fork and request @AdmiralObvious for a pull?

@rfaoro-gpsw
Copy link
Author

@pschwede I can't do that because python 3 doesn't support string decoding.

What about this:

if sys.version_info > (3,) and type(user_dn) == str:
    conn.simple_bind_s(user_dn, password)
else:
    conn.simple_bind_s(user_dn.decode('utf-8'), password)

(Yes, I can create a fork and request @AdmiralObvious for a pr)

@lindycoder
Copy link

I confirm that this is MAY not a py 3 problem, it seems to be an openldap as you said @AdmiralObvious

here's a simple test with a basic openldap container.

docker run --env LDAP_ORGANISATION="My Company" --env LDAP_DOMAIN="my-company.com" --env LDAP_ADMIN_PASSWORD="JonSn0w"  --rm -p 9003:389  osixia/openldap:1.1.11

Then in a python script

import ldap
l = ldap.initialize("ldap://127.0.0.1:9003")
l.simple_bind_s("CN=admin,DC=my-company,DC=com", "JonSn0w")
r = l.search_s("DC=my-company,DC=com", ldap.SCOPE_SUBTREE, 'cn=admin')
print(r)

the result is

[('cn=admin,dc=my-company,dc=com', {'objectClass': [b'simpleSecurityObject', b'organizationalRole'], 'cn': [b'admin'], 'description': [b'LDAP administrator'], 'userPassword': [b'{SSHA}eqGwmHDG99GBl/q3goOay8PmqSOpm4e/']})]

to make this work with openldap you need LDAP_OPENLDAP=True in your config, then in the code here

https://github.com/admiralobvious/flask-simpleldap/blob/master/flask_simpleldap/__init__.py#L190

you return [0][0] which is a string and not a bytes.

so to "keep it the same for everyone else" you could .encode() before returning when LDAP_OPENLDAP=True

That being said here's a disclaimer:

  • I don't think this solution is elegant
  • I only tested with the openldap container
  • My ldap knowledge is about 1/10

@lindycoder
Copy link

i crafted a little monkey patch, works for me:

def _monkey_patch_openldap_string_flask_simpleldap_1_2_0_issue_44(ldap_instance):
    import ldap

    def bind_user(self, username, password):
        user_dn = self.get_object_details(user=username, dn_only=True)

        if user_dn is None:
            return
        try:
            if type(user_dn) == bytes:
                user_dn = user_dn.decode('utf-8')

            conn = self.initialize
            conn.simple_bind_s(user_dn, password)
            return True
        except ldap.LDAPError:
            return

    import types
    ldap_instance.bind_user = types.MethodType(bind_user, ldap_instance)

    return ldap_instance

app = Flask(__name__)
# set configs on app
ldap_auth = _monkey_patch_openldap_string_flask_simpleldap_1_2_0_issue_44(LDAP(app))

# use @ldap_auth.*

Have a nice day :)

@lindycoder
Copy link

@ltpitt not much to explain

doc says do

ldap = LDAP(app)

@app.route('/ldap')
@ldap.login_required
def ldap_protected():
    return 'Success!'

just use my method on the first line

ldap = _monkey_patch_openldap_string_flask_simpleldap_1_2_0_issue_44 (LDAP(app))

@ltpitt
Copy link

ltpitt commented Jun 14, 2018

Thanks for answering, @lindycoder .

Here's my attempt:

from flask import Flask, g, request, session, redirect, url_for
from flask_simpleldap import LDAP

app = Flask(__name__)
app.secret_key = 'dev key'
app.debug = True

app.config['LDAP_USE_SSL'] = True
app.config['LDAP_HOST'] = 'myhost'
app.config['LDAP_BASE_DN'] = 'DC=mydomain,DC=net'
app.config['LDAP_USERNAME'] = 'myuser'
app.config['LDAP_PASSWORD'] = 'mypass'

def _monkey_patch_openldap_string_flask_simpleldap_1_2_0_issue_44(ldap_instance):
    import ldap

    def bind_user(self, username, password):
        user_dn = self.get_object_details(user=username, dn_only=True)

        if user_dn is None:
            return
        try:
            if type(user_dn) == bytes:
                user_dn = user_dn.decode('utf-8')

            conn = self.initialize
            conn.simple_bind_s(user_dn, password)
            return True
        except ldap.LDAPError:
            return

    import types
    ldap_instance.bind_user = types.MethodType(bind_user, ldap_instance)

    return ldap_instance

# set configs on app
ldap = _monkey_patch_openldap_string_flask_simpleldap_1_2_0_issue_44(LDAP(app))

@app.route('/')
@ldap.basic_auth_required
def index():
    return 'Welcome, {0}!'.format(g.ldap_username)

if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0', port=8000)

With correct credentials I get:

Traceback (most recent call last):
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask_simpleldap/__init__.py", line 382, in wrapped
    if not self.bind_user(req_username, req_password):
  File "/vagrant/flask-webapp/ldap-login-test/ldap-login-test.py", line 28, in bind_user
    user_dn = self.get_object_details(user=username, dn_only=True)
  File "/home/ubuntu/test_env/lib/python3.5/site-packages/flask_simpleldap/__init__.py", line 199, in get_object_details
    for k, v in list(records[0][1].items()):
AttributeError: 'list' object has no attribute 'items'

Really thanks for your time and patience

@ltpitt
Copy link

ltpitt commented Jun 14, 2018

I've also added a print(records) before for k, v in list(records[0][1].items()): in __init__.py to better understand what is going on and what I see is:
[(None, ['ldap://ForestDnsZones.mydomain.net/DC=ForestDnsZones,DC=mydomain,DC=net']), (None, ['ldap://DomainDnsZones.mydomain.net/DC=DomainDnsZones,DC=mydomain,DC=net']), (None, ['ldap://mydomain.net/CN=Configuration,DC=mydomain,DC=net'])]

@lindycoder
Copy link

@ltpitt this does not look like the same problem.

The problem in this thread is string vs bytes and my monkey patch fixes this by introducing these lines:

            if type(user_dn) == bytes:
                user_dn = user_dn.decode('utf-8')

(Which must be line 33-34 in your file)

in your stack trace i see this:

  File "/vagrant/flask-webapp/ldap-login-test/ldap-login-test.py", line 28, in bind_user
    user_dn = self.get_object_details(user=username, dn_only=True)

which happens on line 28

So it happens before my fix in my monkey patch so i guess you have another issue completely

@BlackMetalz
Copy link

Merge this commit please: jm66@e537f26

@VicenteIranzoMaestre
Copy link

Same issue, solved applying @lindycoder code.

alexferl pushed a commit that referenced this issue Jul 14, 2019
Fixes #44 - Error in bind_user method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants