1

I'm using the ldap3 module and trying to create a simple Web App to search for users in an ldap server. The user (help desk people normally, doing the searching) must log in and input the user searched for. The code so far creates/binds to the ldap server, and upon finding the searched user, a different page is displayed showing the user's credentials. So far so good.

On the page displaying the credentials, there is a search box, so that that the user can search again for another user. The problem I have now is how to remain logged in via ldap, so that the user only needs to input the searched user (and not again his username and password). I'm thinking I need to parse the conn object in my returns, but somehow this seems a little clumsy. Here is my code:

views.py

def ldap_authentication(request):             
    if request.POST:
        username = request.POST['username']
        LDAP_MODIFY_PASS = request.POST['password']
        searchFilter = request.POST['searchUser']
        LDAP_AUTH_SEARCH_DN = '{}\\{}'.format(settings.DOMAIN_NAME, username)    

        conn = ldap_connect(request, un=LDAP_AUTH_SEARCH_DN, pw=LDAP_MODIFY_PASS)

        attributes_list_keys = ['mail', 'givenName', 'employeeID', 
        'department', 'telephoneNumber', 'st', 'cn', 'l', 'title']

        conn.search(
                search_base=settings.LDAP_AUTH_SEARCH_BASE,
                search_filter= '(cn={})'.format(searchFilter), 
                search_scope=SUBTREE, 
                attributes = attributes_list_keys
                ) 

        entry = conn.entries[0]

        attributes_split = list(entry._attributes.values())
        attr_keys = []
        attr_values = []
        for i in attributes_split:
            attr_keys.append(i.key)
            attr_values.append(i.value)
        attributes = zip(attr_keys, attr_values)

        return render(request, 'search_page.html', {'attributes':attributes})

    return render(request, 'login.html')

def ldap_connect(request, un=None, pw=None):    
    try:
        # Define the server
        server = Server(settings.LDAP_SERVER, get_info=ALL)
        # Connection and Bind operation 
        conn = Connection(server, user=un, password=pw, \
                        auto_bind=True,check_names=True)
        conn.start_tls() # Session now on a secure channel.
        return conn

    except LDAPBindError as e:
        print ("LDAPBindError, credentials incorrect: {0}".format(e))
        logger.debug("LDAPBindError, credentials incorrect: {0}".format(e))
        sys.exit(1) 

    except LDAPSocketOpenError as e:
        print ("LDAPSocketOpenError, LDAP Server connection error: {0}".format(e))
        logger.debug("LDAPSocketOpenError, LDAP Server connection error: {0}".format(e))
        sys.exit(1)  

def search_ldap_user(request):
    if request.POST:
        searchFilter = request.POST['searchUser']
        print ("searchFilter_POST: {0}".format(searchFilter))
        return render(request, 'login.html')

search_page.html

{% extends "base.html" %}
{% load static %}

{% block content %}
<div class="page-header">
    <h2>LDAP Search Page</h2>
</div>
<p><p>
<div class="tab-content">
        <div class="tab-pane active" id="tab1">
            <table class="table table-striped table-condensed" 
            id="orders_open">
            <thead>
            <tr>
                <th>Attribute in LDAP</th>
                <th>Value</th>
            </tr>
            </thead>
            {% for item1, item2 in attributes %}
            <tr>
                <td>{{ item1 }}</td>
                <td>{{ item2 }}</td>
            </tr>
            {% endfor %}
            </table>
        </div>
</div>
<br><br><br><br>

<form method='post' action="{% url 'searchAnother' %}" class="form-signin">
{% csrf_token %}
    <input type="text" class="form-control" name="searchUser" 
    placeholder="Search User" required=""/>
    <button type="submit">Search</button>
</form> 
<form method='post' action="" class="form-signin">{% csrf_token %}
        <br>
        <a href="{% url 'logout' %}" class="btn btn-lg btn-danger btn-
block">LogOut</a>
</form>

{% endblock content %}

login.html

{% extends "base.html" %}
{% load static %}

{% block content %}
<div class="page-header">
        <h2>Welcome to LDAP Search. Please enter your credentials.</h2>
</div>

<div class="wrapper">
    <form method='post' action="" class="form-signin">{% csrf_token %}
        <h3 class="form-signin-heading">Please login</h3>
        <input type="text" class="form-control" name="username" 
        placeholder="Username" required="" autofocus=""/>
        <br>
        <input type="password" class="form-control" name="password" 
        placeholder="Password" required=""/>
        <br>
        <input type="text" class="form-control" name="searchUser" 
        placeholder="Search User" required=""/>
        <br>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
    </form>
</div>

{% endblock content %}

I think the issue is somehow linking the search_ldap_user function in my views.py, with the search_page.html via a conn object but it seems unclear how I am to do this. Has anyone any similar experience with using ldap to authenticate?

UPDATE

So I switched to the django_python3_ldap library, with the following:

settings.py

DOMAIN_NAME = 'OurDomain'
LDAP_AUTH_URL = 'ldap://10.254.9.31:389'                    
LDAP_AUTH_USE_TLS = False                                   
LDAP_AUTH_SEARCH_BASE = 'ou=company-Konzern, dc=ourdomain, dc=de' 
LDAP_AUTH_OBJECT_CLASS = 'inetOrgPerson'                    

LDAP_AUTH_USER_FIELDS = {
    "username": "cn",
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail",
}

# More info: https://github.com/etianen/django-python3-ldap
#LDAP_AUTH_USER_LOOKUP_FIELDS = ("username",)
LDAP_AUTH_USER_LOOKUP_FIELDS = ("cn",)
LDAP_AUTH_CLEAN_USER_DATA = "django_python3_ldap.utils.clean_user_data"
LDAP_AUTH_SYNC_USER_RELATIONS = "django_python3_ldap.utils.sync_user_relations"
LDAP_AUTH_FORMAT_SEARCH_FILTERS = "django_python3_ldap.utils.format_search_filters"
LDAP_AUTH_FORMAT_USERNAME = "django_python3_ldap.utils.format_username_openldap"
LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = 'OurDomain'

#LDAP_AUTH_CONNECTION_USERNAME = 'e123456'
LDAP_AUTH_CONNECTION_USERNAME = 'OurDomain\e123456'
LDAP_AUTH_CONNECTION_PASSWORD = 'abcdefghi'

LDAP_AUTH_CONNECT_TIMEOUT = None
LDAP_AUTH_RECEIVE_TIMEOUT = None

AUTHENTICATION_BACKENDS = (  
    'django_python3_ldap.auth.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_python3_ldap'
]

views.py

def ldap_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        print ("username: {0}".format(username))
        print ("password: {0}".format(password))
        ldap_auth_search_dn = '{}\\{}'.format(settings.DOMAIN_NAME, username)    
        print ("ldap_auth_search_dn: {0}".format(ldap_auth_search_dn))

        user = authenticate(username=username, password=password)
        #user = authenticate(username=ldap_auth_search_dn, password=password)
        print ("user: {0}".format(user))
        if user and user.is_active:
            print ("user.is_active!!")
            login(request, user, backend='django_python3_ldap.auth.LDAPBackend')

    return render(request, 'login_ldap.html')

As you can see from commented out lines, I've tried various permutations of configurations in the settings.py and there are still some issues unclear to me:

  1. Is this obligatory in settings.py: LDAP_AUTH_OBJECT_CLASS ?
  2. Which is the correct LDAP_AUTH_USER_LOOKUP_FIELDS to use?
  3. Same as 2, but for LDAP_AUTH_CONNECTION_USERNAME (is this supposed to include the Domain)?

The following error message is thrown when I use the command

python ./manage.py ldap_sync_users

Error:

CommandError: Could not connect to LDAP server

When I don't use the ldap_sync_users commands and simply run the server, there appears to be no connection, since the user in the print statement comes back as None. I know with the credentials that a connection works, as my previous code using the ldap3 library works. The only thing I can think of is the LDAP_AUTH_SEARCH_BASE is not the same between both libraries.

pymat
  • 1,090
  • 1
  • 23
  • 45
  • You should use LDAP in conjunction with the Django authentication framework. – Daniel Roseman Oct 19 '17 at 09:15
  • I think you mean something like adding a django authentication backend for LDAP Authentication (eg:: /Project/Middleware/ldap_interface.py) and configure the backend in settings.py for django to use it for all logins. – pymat Oct 19 '17 at 11:08
  • ldap3. It's not the best of libraries with documentation/examples/forum posts tbh...but the other functionalities I require are there, so... – pymat Oct 19 '17 at 11:56
  • @The_Cthulhu_Kid: how do you currently configure the auth in your backend/middleware? – pymat Oct 19 '17 at 11:57
  • Use this: https://github.com/etianen/django-python3-ldap it is built on ldap3 so you want need to change much. – The_Cthulhu_Kid Oct 19 '17 at 12:01
  • @The_Cthulhu_Kid: can you point me to any tutorials/templates for django-python3-ldap? There doesn't seem to be much in the way of documentation compared to ldap3 – pymat Oct 19 '17 at 14:34

1 Answers1

0

Using django-python3-ldap which is built upon ldap3 I authenticate like this just using the basic settings described in the repo:

from django.contrib.auth import authenticate, login

def ldap_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        user = authenticate(username=username, password=password)
        # Test return values
        if user and user.is_active:
            login(request, user, backend='django_python3_ldap.auth.LDAPBackend')

            return JsonResponse({'success': 'YES'})
        return JsonResponse({'success': 'NO'})

It is really no different than the normal authentication process.

The_Cthulhu_Kid
  • 1,839
  • 1
  • 31
  • 42
  • @The_Cthlhu_Kid: looks good. I take it you do this in some kind of backend/middleware and not in view.py. – pymat Oct 20 '17 at 09:18
  • @The_Cthlhu_Kid: I've updated my original post after UPDATE. Let me know what you think. – pymat Oct 30 '17 at 12:50
  • @The_Cthlhu_Kid: After trying the ldap_sync_users command, I get: CommandError: Could not connect to LDAP server. If I just run the server. I the page loads as normal, no error, my print statements (see the view.py) show my username, password, and user=None (which if it connected, it shouldn't be None9. What you mean exactly you changed the LDAP_AUTH_SEARCH_BASE? – pymat Oct 30 '17 at 13:31
  • @The_Cthlhu_Kid, yes no difference with/without spaces :-/ From what I understand, if there is no connectivity issue then the ldap_sync_users command should work. – pymat Oct 30 '17 at 13:43
  • I tried with double back slash...no change I'm afraid. I also tried to double what I had in the views.py but also no change. What do you mean if I am using AD (and so not LDAP)? The connection works with the ldap3 library, but for some reason I can't get it to work with this particular library. It's strange :-/ – pymat Oct 31 '17 at 09:30
  • The GUI to login with is Softerra LDAP Administratory, my own PC is a Windows P. – pymat Oct 31 '17 at 10:03
  • always AD but with ldap so tht means no AD join – pymat Nov 03 '17 at 10:57
  • sorry I just copied and pasted the answer from our Admin :-/ Yes of course, but it's AD (not OpenLdap), from what I understand. – pymat Nov 03 '17 at 11:02
  • I now have some progres, thank you for that. I realised there was progress when the python ./manage.py ldap_sync_users didn't throw an error any more.From then on I realised that the user object keeps on returning None. I have a standard form which a user is supposed to enter their un & pw. However when I use runserver and refresh my page, the contents of the view (I have print statements there to assist me) display the us & pw, along with user=None. I think this has something to do with the ldap_sync_users command and has cached the info. – pymat Nov 03 '17 at 12:01
  • That means the "user = authenticate(username=username, password=password)" should also not come back as None too, right? I mean, the credentials I use to log in to the "LDAP Server" are correct (as per the Django_python3_ldap library), but in this case, I'm getting no error messages with the LDAP Server not being found (good), but then I can't access the next page, which depends on logging into LDAP (not good). – pymat Nov 03 '17 at 12:20
  • something odd has now happened. I now don't receive an error message if I purposely change the LDAP_AUTH_URL parameter to a false ip address. When I change my code, and refresh, the username and password (before filling anything out in my fields) are given: Quit the server with CTRL-BREAK. username: myUser password: myPW ldap_auth_search_dn: company\myUser user: None – pymat Nov 06 '17 at 08:23
  • I created a new thread: https://stackoverflow.com/questions/47134288/no-confirmation-of-authenticateusername-username-password-password-in-django – pymat Nov 06 '17 at 10:05