Geonode: LDAP error when user login

Created on 6 Nov 2020  路  16Comments  路  Source: GeoNode/geonode

Expected Behavior

Connect to geonode with LDAP account

Actual Behavior

Web app crash when user submit login and password

Steps to Reproduce the Problem

  1. Add LDAP support to geonode : https://docs.geonode.org/en/master/advanced/contrib/
  2. Connect with LDAP user
  3. Web application crash:
TypeError at /account/login/
sequence item 0: expected str instance, bytes found
Request Method: POST
Request URL:    http://geonode-dev.aesn.fr/account/login/?next=/
Django Version: 2.2.12
Exception Type: TypeError
Exception Value:    
sequence item 0: expected str instance, bytes found
Exception Location: /usr/local/lib/python3.7/site-packages/geonode_ldap/utils.py in get_ldap_groups_map, line 56
Python Executable:  /usr/local/bin/uwsgi
Python Version: 3.7.6
Python Path:    
['/usr/src/app',
 '.',
 '',
 '/usr/local/lib/python37.zip',
 '/usr/local/lib/python3.7',
 '/usr/local/lib/python3.7/lib-dynload',
 '/usr/local/lib/python3.7/site-packages',
 '/usr/src/app']
Server time:    ven, 6 Nov 2020 12:54:20 +0000

Specifications

  • GeoNode version: 3.0
  • Installation method (manual, GeoNode Docker, SPCGeoNode Docker): Docker
  • Platform: CentOS
  • Additional details:

When I change line 55 in /usr/local/lib/python3.7/site-packages/geonode_ldap/utils.py like this:
Before

group_name = " ".join(
            attributes[settings.LDAP_GROUP_NAME_ATTRIBUTE])

After

group_name = b" ".join(
            attributes[settings.LDAP_GROUP_NAME_ATTRIBUTE])

Everything work fine (I have an error but it's my config : Group matching query does not exist. )

contrib-apps

All 16 comments

@naub1n thanks, I pushed your patch to geonode-contribs module

https://github.com/GeoNode/geonode-contribs/commit/ecb41e6319f00dd9308739463547ab5b78f22e1d

@afabiani wait! I have strange behavior

With original utils.py file.
When I login for the first time, web app crash.
But when I return to main page and I try again to login, everything is OK
When I logout and then loggin, it's OK.

I don't understand ...

Anything from the geonode logs?

I change the code like this:
Before:

group_name = " ".join(
            attributes[settings.LDAP_GROUP_NAME_ATTRIBUTE])

After:
group_name = b" ".join(attributes[settings.LDAP_GROUP_NAME_ATTRIBUTE])

And I have the same error but not on the same line:

TypeError at /account/login/
sequence item 0: expected str instance, bytes found
Request Method: POST
Request URL:    http://geonode-dev.aesn.fr/account/login/?next=/
Django Version: 2.2.12
Exception Type: TypeError
Exception Value:    
sequence item 0: expected str instance, bytes found
Exception Location: /usr/local/lib/python3.7/site-packages/geonode_ldap/utils.py in get_ldap_groups_map, line 56
Python Executable:  /usr/local/bin/uwsgi
Python Version: 3.7.6
Python Path:    
['/usr/src/app',
 '.',
 '',
 '/usr/local/lib/python37.zip',
 '/usr/local/lib/python3.7',
 '/usr/local/lib/python3.7/lib-dynload',
 '/usr/local/lib/python3.7/site-packages',
 '/usr/src/app']
Server time:    ven, 6 Nov 2020 14:31:38 +0000

The geonode logs :

search_s('ou=agents,dc=aesn,dc=fr', 2, '(&(uid=%(user)s)(objectClass=person))') returned 1 objects: cn=nicolas aubin,ou=agents,dc=aesn,dc=fr
cn=nicolas aubin,ou=agents,dc=aesn,dc=fr does not have a value for the attribute mailPrimaryAddress
sequence item 0: expected str instance, bytes found while authenticating aubin_ni
Internal Server Error: /account/login/
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py", line 45, in _wrapper
    return bound_method(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/decorators/debug.py", line 76, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/allauth/account/views.py", line 138, in dispatch
    return super(LoginView, self).dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/allauth/account/views.py", line 81, in dispatch
    **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/generic/base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/allauth/account/views.py", line 103, in post
    if form.is_valid():
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 185, in is_valid
    return self.is_bound and not self.errors
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 180, in errors
    self.full_clean()
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 382, in full_clean
    self._clean_form()
  File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 409, in _clean_form
    cleaned_data = self.clean()
  File "/usr/local/lib/python3.7/site-packages/allauth/account/forms.py", line 179, in clean
    **credentials)
  File "/usr/local/lib/python3.7/site-packages/allauth/account/adapter.py", line 489, in authenticate
    user = authenticate(request, **credentials)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/usr/local/lib/python3.7/site-packages/django_auth_ldap/backend.py", line 153, in authenticate
    user = self.authenticate_ldap_user(ldap_user, password)
  File "/usr/local/lib/python3.7/site-packages/geonode_ldap/backend.py", line 114, in authenticate_ldap_user
    self._get_or_create_user(ldap_user)
  File "/usr/local/lib/python3.7/site-packages/geonode_ldap/backend.py", line 184, in _get_or_create_user
    self._mirror_groups(ldap_user)
  File "/usr/local/lib/python3.7/site-packages/geonode_ldap/backend.py", line 194, in _mirror_groups
    slug_map = utils.get_ldap_groups_map(user)
  File "/usr/local/lib/python3.7/site-packages/geonode_ldap/utils.py", line 56, in get_ldap_groups_map
    sanitized_name = sanitize_group_name(group_name)
TypeError: sequence item 0: expected str instance, bytes found
[pid: 996|app: 0|req: 14/172] 10.0.9.11 () {62 vars in 1339 bytes} [Fri Nov  6 14:31:38 2020] POST /account/login/?next=/ => generated 204586 bytes in 375 msecs (HTTP/1.1 500) 7 headers in 252 bytes (1 switches on core 0)
worker 9 lifetime reached, it was running for 3601 second(s)
worker 10 lifetime reached, it was running for 3601 second(s)
worker 11 lifetime reached, it was running for 3601 second(s)
worker 12 lifetime reached, it was running for 3601 second(s)
worker 13 lifetime reached, it was running for 3601 second(s)
worker 14 lifetime reached, it was running for 3601 second(s)
worker 15 lifetime reached, it was running for 3601 second(s)
worker 16 lifetime reached, it was running for 3601 second(s)
Respawned uWSGI worker 10 (new pid: 1254)
Respawned uWSGI worker 9 (new pid: 1255)
Respawned uWSGI worker 11 (new pid: 1256)
Respawned uWSGI worker 12 (new pid: 1257)
Respawned uWSGI worker 13 (new pid: 1258)
Respawned uWSGI worker 14 (new pid: 1259)
Respawned uWSGI worker 15 (new pid: 1260)
Respawned uWSGI worker 16 (new pid: 1261)

Try changing

sanitized_name = sanitize_group_name(group_name)

to

sanitized_name = sanitize_group_name(group_name.decode('UTF-8'))

I suspect docker volume.

I mount only local_settings.py from host file instead of mount /usr/src/app folder from samba share.

Everythings seems to work fine. No error when loggin for the first time.

However, there are another strange thing.
Django ignore variable LDAP_GROUP_PROFILE_MEMBER_ATTR=uniqueMember
geonode.log:

search_s('ou=agents,dc=aesn,dc=fr', 2, '(&(uid=%(user)s)(objectClass=person))') returned 1 objects: cn=nicolas aubin,ou=agents,dc=aesn,dc=fr
search_s('ou=groupes,dc=aesn,dc=fr', 2, '(&(cn=geonode)(|(member=cn=nicolas aubin,ou=agents,dc=aesn,dc=fr)))') returned 0 objects: 

I will create a new issue for this.

cn=nicolas aubin,ou=agents,dc=aesn,dc=fr does not have a value for the attribute mailPrimaryAddress
sequence item 0: expected str instance, bytes found while authenticating aubin_ni

Any way to debug the contrib app? LDAP Auth is complicated and it is hard to see if the error resides in the geonode contrib code / the django LDAP code / or the LDAP directory you map to. For example there is no value for mailPrimaryAddress, which could lead null values and errors in the profile creation process.

@gannebamm this is not an error, it's like a Warning.
There is no mailPrimaryAddress in my LDAP test database. But it's not a problem, geonode can work without this value.
I will try new test this week when i will have little free time.

I did other tests:

  1. Stop django and postgreSQL containers
  2. Delete all files in staticsand dbdatavolumes
  3. Delete CIFS volume /usr/src/app
  4. Bind only local local_settings.py in /usr/src/app/geonode/local_settings.py

Everything work fine with LDAP connexion.
CIFS volume was the problem.

Don't do that (where appis CIFS volume):

volumes:
      - app:/usr/src/app

EDIT : mount CIFS is not a problem

@afabiani , I found other problem in utils.py
LDAPS groups no have same name in geonode after GeoNode/geonode-contribs@ecb41e6.

This is actual result:

>>> conn = ldap.initialize("ldap://openldap:389/")
>>> conn.simple_bind_s("cn=admin,dc=aesn,dc=fr","password")
(97, [], 1, [])
>>> remote_groups = conn.search_s(base="ou=groupes,dc=aesn,dc=fr",scope=ldap.SCOPE_SUBTREE,filterstr="(cn=geonode_admin)")
>>> print(remote_groups)
[('cn=geonode_admin,ou=groupes,dc=aesn,dc=fr', {'cn': [b'geonode_admin'], 'objectClass': [b'groupOfUniqueNames', b'top'], 'uniqueMember': [b'cn=Nicolas AUBIN,ou=agents,dc=aesn,dc=fr']})]
>>> result = {}
>>> for cn, attributes in remote_groups:
...     group_name = b" ".join(attributes["cn"])
...     sanitized_name = sanitize_group_name(group_name)
...     result[slugify(sanitized_name)] = {"original": group_name,"sanitized": sanitized_name,}
... 
>>> print(result)
{'bgeonode_admin': {'original': b'geonode_admin', 'sanitized': b'geonode_admin'}}
>>> 

resultis a byte value and slugify(sanitized_name) have bad value ( bgeonode_admin)
To correct this problem, you can had .decode("utf-8") to group_name like this:

>>> conn = ldap.initialize("ldap://openldap:389/")
>>> conn.simple_bind_s("cn=admin,dc=aesn,dc=fr","password")
(97, [], 1, [])
>>> remote_groups = conn.search_s(base="ou=groupes,dc=aesn,dc=fr",scope=ldap.SCOPE_SUBTREE,filterstr="(cn=geonode_admin)")
>>> print(remote_groups)
[('cn=geonode_admin,ou=groupes,dc=aesn,dc=fr', {'cn': [b'geonode_admin'], 'objectClass': [b'groupOfUniqueNames', b'top'], 'uniqueMember': [b'cn=Nicolas AUBIN,ou=agents,dc=aesn,dc=fr']})]
>>> result = {}
>>> for cn, attributes in remote_groups:
...     group_name = b" ".join(attributes["cn"]).decode("utf-8")
...     sanitized_name = sanitize_group_name(group_name)
...     result[slugify(sanitized_name)] = {"original": group_name,"sanitized": sanitized_name,}
... 
>>> print(result)
{'geonode_admin': {'original': 'geonode_admin', 'sanitized': 'geonode_admin'}}

result is now a string value and LDAP groups names are correct in geonode

@naub1n it is sufficient to remove the 'b' in front of join

group_name = " ".join(attributes["cn"])

@afabiani no because remote_groups have bytes values and join can't work with string value.

>>> print(remote_groups)
[('cn=geonode_admin,ou=groupes,dc=aesn,dc=fr', {'cn': [b'geonode_admin'], 'objectClass': [b'groupOfUniqueNames', b'top'], 'uniqueMember': [b'cn=Nicolas AUBIN,ou=agents,dc=aesn,dc=fr']})]

This is why I had my first message error: TypeError: sequence item 0: expected str instance, bytes found

>>> conn = ldap.initialize("ldap://openldap:389/")
>>> conn.simple_bind_s("cn=admin,dc=aesn,dc=fr","password")
(97, [], 1, [])
>>> remote_groups = conn.search_s(base="ou=groupes,dc=aesn,dc=fr",scope=ldap.SCOPE_SUBTREE,filterstr="(cn=geonode_admin)")
>>> print(remote_groups)
[('cn=geonode_admin,ou=groupes,dc=aesn,dc=fr', {'cn': [b'geonode_admin'], 'objectClass': [b'groupOfUniqueNames', b'top'], 'uniqueMember': [b'cn=Nicolas AUBIN,ou=agents,dc=aesn,dc=fr']})]
>>> result = {}
>>> for cn, attributes in remote_groups:
...     group_name = " ".join(attributes["cn"])
...     sanitized_name = sanitize_group_name(group_name)
...     result[slugify(sanitized_name)] = {"original": group_name,"sanitized": sanitized_name,}
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: sequence item 0: expected str instance, bytes found
>>> print(result)
{}

I see... pushing the change accordingly. Thanks for checking.

After testing, everything works fine.
mount CIFS volume /usr/src/app is not a problem

very good

Was this page helpful?
0 / 5 - 0 ratings

Related issues

capooti picture capooti  路  8Comments

t-book picture t-book  路  9Comments

HaidarZ picture HaidarZ  路  4Comments

kikislater picture kikislater  路  7Comments

hishamkaram picture hishamkaram  路  9Comments