Category: django

Upgrading SQLite on CentOS to 3.8.3 or Later

Let me guess you are using django and may have just done an upgrade to django 2.2.x in order to stay up to date or for posterity on vulnerabilities.

Unfortunately CentOS only has v3.7.17 in their repos.

So you need to install v3.8.3 or the latest from source.

To do that, you can install from source (I’m not sure how to use the precompile binaries)

  1. Download the source code from sqlite downloads

cd /opt
wget https://www.sqlite.org/2019/sqlite-autoconf-3280000.tar.gz
tar -xzf sqlite-autoconf-3280000.tar.gz
cd sqlite-autoconf-3280000
./configure
make
sudo make install

You have to log out and relog in for it to change.
However this doesn’t help as:


>>> import sqlite3
>>> sqlite3.sqlite_version
'3.7.17'

Adding Time taken to respond to a request in the header of a Django Rest Framework Response. A suitable place to use a Mixin or not?

Ever wanted to add the time taken for a response to your API, so the client knows how long that takes?
I first noticed this cool feature on AWX, a management platform for ansible playbooks.

There’s response headers looked like this:

awx-api-timed-response

So I checked out their source code and copied how they did it:

In generics.py the package extends the base DRF ApiView class and adds a bit of stuff, the important stuff for us is:


    def finalize_response(self, request, response, *args, **kwargs):
        ...
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        response['X-API-Node'] = settings.CLUSTER_HOST_ID
        ...
        
    def initialize_request(self, request, *args, **kwargs):
        ...
        self.time_started = time.time()
        ...

So I created a class extending for ApiView:


class APIView(views.APIView):
    '''
    Add timing to the base APIView class
    '''
    def initialize_request(self, request, *args, **kwargs):
        self.time_started = time.time()
        return super().initialize_request(request, *args, **kwargs)
        
    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        if time_started:
            time_elapsed = time.time() - self.time_started
            response['X-API-Time'] = '%0.3fs' % time_elapsed
        return response

Now all I needed to do was entend from my_package.ApiView instead of restframework.views.ApiView and I would get an X-API-Time header.

It worked well for a while, but then I needed to use more Generic Class Based Views (Which extend from ApiView)…check out CDRF. So I could just add these methods, to the extended versions of restframework.generics.ListCreateView etc.

But that just doesn’t feel right. Simply because I can see I am repeating myself multiple times. I just be able to define this functionality once and have all similar objects (those that extend from ApiView) have that functionality…

Using a Mixin to add the Time taken to Respond

I just created a mixin, by extracting those functions into a single class that inherits from nothing:


class TimedAPIMixin:
    def initialize_request(self, request, *args, **kwargs):
        self.time_started = time.time()
        return super().initialize_request(request, *args, **kwargs)

    def finalize_response(self, request, response, *args, **kwargs):
        response = super().finalize_response(request, response, *args, **kwargs)
        time_started = getattr(self, 'time_started', None)
        if time_started:
            time_elapsed = time.time() - self.time_started
            response['X-API-Time'] = '%0.3fs' % time_elapsed
        return response

Using them in the other views:

 

 


from rest_framework import views
from rest_framework import generics


class APIView(TimedAPIMixin, views.APIView):
    pass


class ListCreateAPIView(TimedAPIMixin, generics.ListCreateAPIView):
    pass


class ListAPIView(TimedAPIMixin, generics.ListAPIView):
    pass

It only works if the mixin is the first thing it inherits from, otherwise the first class will take preference.

Authenticated Functional Tests with Selenium and Django

In the Test Driven Development Book for Python and Django by Harry Percival called Obey The Testing Goat, there is a chapter about enhancing the functional test base class and adding pre-authentication so you don’t need to login via the login screen with Selenium.

It uses a custom Email Authentication Backend, but I needed to implement this on a standard: django.contrib.auth.backends.ModelBackend.

My First Attempt


    def create_pre_authenticated_session(self, user):
        '''Create an authenticated user quickly'''
        session = SessionStore()
        session[SESSION_KEY] = user.pk
        session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0]
        session.save()
        # visit domain (404 quickest)
        self.browser.get(self.live_server_url + "/404_no_such_url/")
        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
        ))

I ran my functional test and something weird was happening, the cookie was getting killed right after this method is called an going to any page.

So I compared based on the cookie itself compared to one that existed in firefox developer tools.

The difference was the httpOnly thingy. So I added it…


        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
            secure=False,
            httpOnly=True
        ))

Nothing changed, the cookie was still gone.

So then I compared an existing decoded session with the one created via the method above.

To find the decoded session:

$ python manage.py shell
[...]
In [1]: from django.contrib.sessions.models import Session

# substitute your session id from your browser cookie here
In [2]: session = Session.objects.get(
    session_key="8u0pygdy9blo696g3n4o078ygt6l8y0y"
)

In [3]: print(session.get_decoded())
{'_auth_user_id': 'obeythetestinggoat@gmail.com', '_auth_user_backend':
'accounts.authentication.PasswordlessAuthenticationBackend'}

The Session Difference

I noticed there was a difference a working session looked like this:

{'_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': '6a34097f6dab2a1fc68f262e9e67186d2ad5ba93'}

whereas the one I created looked like this:

{'_auth_user_id': 1, '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend'}

So the _auth_user_hash was a problem. I search the django source and found it in auth.

So I set the hash session key with: session[HASH_SESSION_KEY] = user.get_session_auth_hash()

It then worked.

The Solution


    def create_pre_authenticated_session(self, user):
        '''Create an authenticated user quickly'''
        session = SessionStore()
        session[SESSION_KEY] = user.pk
        session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0]
        session[HASH_SESSION_KEY] = user.get_session_auth_hash()
        session.save()
        # visit domain (404 quickest)
        self.browser.get(self.live_server_url + "/404_no_such_url/")
        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
            secure=False,
            httpOnly=True
        ))