2

I'm quite new to Django, and have been assigned the task of building a web application that authenticates a user against LDAP, once authneticated the user can search for a person/group in the LDAP Server. In the last few weeks I've been stuggling with how to set this up and wrote a few posts already, but they didn't come up with anything. I realised, because I originally wanted to perform all the LDAP authentication tasks in my views.py, that this approach was not how Django intended. Instead, these kind of authentication functions are intended to be built as middleware, which has opened up a whole new can of worms for me.

I realised in any case I was restricted in performing this whole task in view.py, because this prevented a user from switching to a new page and still remaining authenticated in LDAP (I found the parsing of the conn object a bit clumsy, see here).

Anyway, after reading around and watching several informative videos, like here and here, I managed to get the first step of my middleware working. However, there are some processes occuring in Django that are at work, and I don't fully understand why. Let me explain, but first the relevant code:

ldap_interface.py

try:
    from ldap3 import Server, Connection, ALL, SUBTREE, LDAPBindError, LDAPSocketOpenError, AUTH_SIMPLE, STRATEGY_SYNC, \
        MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE
except ImportError as e:
    raise ImportError("Import of ldap3 module unsuccessful: {0}".format(e))

from django.conf import settings

##########################################
### ERROR HANDLING ###
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

#Create file handler
handler = logging.FileHandler('C:\\Users\\me\\Desktop\\LDAP_auth.log')
handler.setLevel(logging.DEBUG)

#Create logging format
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

#Add handlers to the logger
logger.addHandler(handler)
##########################################

class LDAP_Auth_Backend:
    def __init__(self, get_response, un=None, pw=None):
        print("A:{0}".format(get_response))
        self.get_response = get_response
        print("B:{0}".format(self.get_response))

    def __call__(self, request):
        response =  self.get_response(request)
        print("D:{0}".format(response))
        return response

    """
    def process_request(self, request):
        print ("Middleware executed")
    """

    def ldap_connect(self, 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))
            return HttpResponse('Invalid login')
        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)  

settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'UserAuth.Middleware.ldap_interface.LDAP_Auth_Backend',
]

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(DOMAIN_NAME, username) # Should be the same as conn.extend.standard.who_am_i() 
        conn = LDAP_Auth_Backend.ldap_connect(request, un=LDAP_AUTH_SEARCH_DN, pw=LDAP_MODIFY_PASS)
        print ("C: {0}".format(conn))

        conn.search(
                search_base=LDAP_AUTH_SEARCH_BASE,
                search_filter= '(cn={})'.format(searchFilter), # This is the user being searched for
                search_scope=SUBTREE # Other parameters are BASE & LEVEL 
                ) 
        entry = conn.entries[0]
        dn = entry._dn
        split_dn = dn.split(",")
        request.session.set_expiry(10)
        return render(request, 'search_page.html', {'dn':split_dn})

    return render(request, 'login.html')

urls.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', ldap_authentication, name='index'),
]

As mentioned, after getting to the logging page, entering the correct credentials and search name, the LDAP search comes back with the information on cn, dc, etc. on a separate page. This is so far intended and will be expanded in due course.

Console

System check identified no issues (0 silenced).
August 02, 2017 - 15:44:30
Django version 1.11, using settings 'UserAuth.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
A:<function BaseHandler._get_response at 0x000000B93CE89C80>
B:<function BaseHandler._get_response at 0x000000B93CE89C80>
A:<function BaseHandler._get_response at 0x000000B93B863378>
B:<function BaseHandler._get_response at 0x000000B93B863378>
C: ldap://an_ip:389 - cleartext - user: random_name - bound - open - <local: another_ip:55135 - remote: etc> - tls started - listening - SyncStrategy
D:<HttpResponse status_code=200, "text/html; charset=utf-8">
[02/Aug/2017 15:44:41] "POST / HTTP/1.1" 200 1867
[02/Aug/2017 15:44:41] "GET /static/bourbon HTTP/1.1" 404 1634 

I tested what was going on by placing print statements in my code, that's where you can see for example "A", "B", "C", "D".

Questions:

  1. Once the runserver command is ran, and before login in, the "A", "B" print statements are called, when the middleware script is ran, but as you can see they are called not once, but twice. Why?
  2. Next the "C" statement is printed. In the views.py file, the conn object is created by accessing the middleware class (with username and password). Within this class is the __call__ method, where the "D" statement lies. I expected that this "D" is first printed out, then goes on to "C". But this is not the case. Why is it first C then D?
  3. I understand after much trial and error, that both the __init__ and __call__ methods are required in order to develop the middleware class. Are these methods absolutely obligatory to be included for this middleware auth class? I didn't come across this particular instruction in any of the documents I've read so far, whereby these 2 methods must be included.
pymat
  • 1,090
  • 1
  • 23
  • 45

1 Answers1

0

the problem number 1) I can't explain why the __init__ is called twice.

2) Whatever is called before the self.get_response(request) in the middleware's __call__ method is executed before the view is called. What comes after the self.get_response(request) gets executed after the view. This explains the behavior. This is documented here.

3) You could use the old style middleware which will not be supported after django 2.1 otherwise yes, these are required.

Borko Kovacev
  • 1,010
  • 2
  • 14
  • 33