1

I'd like to setup an LDAP Authentication Backend in Django, and I've already used ldap3 to confirm a bind, with success. I'm now realising that writing a class for my LDAP Backend with just ldap3 is not so straightforward, and that installing django_auth_ldap could be another route to explore.

I've tested already some code to create a bind to the LDAP "server", and then perform a simple search. All okay. This method I tested is outside of my Django framework.

When implementing the same method into my Django framework, I encounter an issue when the method gets called. Because of the print statements, I know the view is getting called as exptected, but the part of the code whereby the first "if" statement should be executed, does not get called unless I change from "POST" to "GET". But then this seems to create the next issue (when I set to "GET", so to force the next lines to be executed), because I'd like that a login page then gets called, where I can then input my LDAP credentials, ultimately confirming the LDAP connection. Here is my code:

 views.py

 def login_ldap(request):
    LDAP_SERVER = '10.222.4.88'
    searchFilter='random'
    print (request)
    print (LDAP_SERVER)  

print ("request.method:{0}".format(request.method))

if request.method == "GET":
    print ("if statement executed")
    username = request.GET['username']
    print ("U:{0}".format(username))
    password = request.GET['password']
    print ("P:{0}".format(password))

    # Define the server and bind_dn
    server = Server(LDAP_SERVER, get_info=ALL) 
    bind_dn = 'cn={0}, ou=Prod, ou=Extern, ou=User, ou=ABC, dc=DEF, dc=com'.format(username)
    # Define the Connection  
    conn = Connection(server, bind_dn, password, auto_bind=True) # Use raise_exceptions=True for exceptions 
    print ("search: {0}",format(conn.search))
    print ("conn: {0}",format(conn))
    conn.start_tls() #Session now on a secure channel. See output from following print statement and "tls started"
    print ("conn_tls: {0}",format(conn))
    d = conn.extend.standard.who_am_i() 
    print (d)
    #print ("Server Info: {0}",format(server.info))
    conn.open()
    conn.bind()

    # The LDAP search base for looking up users.
    LDAP_AUTH_SEARCH_BASE = "ou=ABC, dc=DEF, dc=com"

    if conn.bind():
        conn.search(
                 search_base=LDAP_AUTH_SEARCH_BASE,
                 search_filter= '(cn={})'.format(searchFilter), # This is the user being searched for
                 search_scope=SUBTREE # BASE & LEVEL also possible settings
                    ) 
        entry = conn.entries[0] 
        res = conn.bind()       
    print (res)

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

The error message received on my webpage:

MultiValueDictKeyError at /login_ldap/
"'username'"
Request Method: GET
Request URL:    http://127.0.0.1:8000/login_ldap/
Django Version: 1.11
Exception Type: MultiValueDictKeyError
Exception Value:    
"'username'"

I assume this is related to the GET and no longer the POST method. Why is the request.method being automatically set to GET and not POST when implementing this in Django? Is this class for the authentication in the correct location, in the views.py file or should there be a separate file for this? What should be included in the settings.py exactly (related to BACKEND_AUTH)?

EDIT:

views.py 

def login_view(request): 
if request.POST: 
username = request.POST['username'] 
print ("U:{0}".format(username)) 
password = request.POST['password'] 
print ("P:{0}".format(password)) 
user = authenticate(username=username, password=password) 
print (user) 
if user is not None: 
if user.is_active: 
login(request, user) 
return redirect('index') 
else: 
messages.error(request, "User is not active in Database") 
else: 
print ("Please check your credentials!") 
messages.error(request, "Please check your username and password!") 
return render(request, 'login.html') 


index.html 

<div class="row"> 
<div class="col-lg-3"></div> 
<div class="col-lg-6"><H1>This is the public page, Please <a href="{% url 'login_ldap' %}">Login</a></H1></div> 
</div> 


urls.py 

urlpatterns = [ 
url(r'^admin/', admin.site.urls), 
url(r'^login_ldap/$', login_ldap, name='login_ldap'), 
url(r'^login/$', login_view, name='login'), 
url(r'^logout/$', logout_view, name='logout'), 
url(r'^change_password/$', change_password, name='change_password'), 
url(r'^$', index, name='index'), 
] 

settings.py 

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

search_page.html

{% load static %} 
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> 
<link href='{% static "login.css" %}' rel="stylesheet"> 

<div class="wrapper"> 
<form method='post' action="" class="form-signin">{% csrf_token %} 
<h2 class="form-signin-heading">Please login</h2> 
<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> 
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button> 
</form> 
</div>
pymat
  • 1,090
  • 1
  • 23
  • 45
  • But this doesn't seem to have anything to do with LDAP itself. You should show how you're submitting the username/password data to that view. – Daniel Roseman Jun 23 '17 at 19:22
  • I had another view method (see new Edit "login_view") which relied on the Django Backend, and the login.html. In my index.html I changed the tag from url 'login' to url 'login_ldap' so to redirect to the view login_ldap, which works, but the login page to input username/password doesn't get rendered. – pymat Jun 24 '17 at 14:03
  • I can't really understand what you're trying to do here. Obviously changing to GET will cause that error when you first render the page, because you *haven't* sent any parameters. But you shouldn't have changed to GET in the first place; the way to submit data to a page is via POST. As I asked, please show the template for the login_ldap page. – Daniel Roseman Jun 24 '17 at 14:06
  • Isn't this a classic case of append slash redirect? –  Jun 24 '17 at 14:22
  • Yes exactly, I only set to GET from POST because otherwise this part of the code ("if request.method == "GET":") doesn't execute. When the other login_view method is called, the request.method is always POST, but for some reason the request.method changes to GET, hence the "if request.method == "POST":" doesn't execute. The search_page.html is added to my Edit. In my index.html I change from url 'login' to url 'login_ldap' to conform with the urls.py. – pymat Jun 24 '17 at 14:27
  • @Melvyn: I think you mean that my url is not directing as I want it to? I don't believe this is the issue, because the login_ldap method is getting called, it's within this method that the "if request.method == "POST"" is not getting called, because the request.method seems to be set to GET. – pymat Jun 25 '17 at 10:09
  • Your pattern for `login_ldap` ends with a slash. If you request it without a slash, as in `http://localhost/login_ldap`, then it silently gets redirected as a GET request. Any attempts to refresh, will still be a GET request, until you retype the URL correctly with slash in the address bar. If this is not the case, then please update your answer with the exact flow and at which point the problem exists. –  Jun 25 '17 at 10:31
  • From your edit's code, the flow is 1) *index* => 2) *login_ldap* (GET) => 3) *login_ldap* (POST). But I think your problem is at 2), not at 3). –  Jun 25 '17 at 10:46

1 Answers1

0

You can use get() function to get the data.

if request.method == "GET":
    print ("if statement executed")
    username = request.GET.get('username')

For more reference you can see here,

MultiValueDictKeyError in Django

And about POST method you may require to import the csr_exempt and use the decorator before your view.

from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def myView(request):