I have basic authorization in two places in my code. I want to separate this as one function and not repeat code.
My app:
from django.shortcuts import render
from django.views.generic import View
from django.http import HttpResponse, Http404
from django.contrib.auth import authenticate
from django.core.exceptions import PermissionDenied
import base64
from notes.models import Note, load_initial_data
class NoteListView(View):
def filter_queryset(self, query_set):
query_params = self.request.GET
if 'board' in query_params:
query_set = query_set.filter(board=query_params['board'])
return query_set
def get(self, request):
load_initial_data()
query_set = self.filter_queryset(Note.objects.all())
basic_auth = True
# this lines below !
if basic_auth:
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
request.user = user
if not request.user.is_staff:
raise PermissionDenied
return HttpResponse(query_set)
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % "Basic Auth Protected"
return response
else:
return HttpResponse(query_set)
class NoteView(View):
def get_object(self, obj_id):
try:
return Note.objects.get(id=int(obj_id))
except IndexError:
raise Http404
def get(self, request, note_id):
load_initial_data()
basic_auth = True
#this lines below
if basic_auth:
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
request.user = user
return HttpResponse(self.get_object(note_id))
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % "Basic Auth Protected"
return response
else:
return HttpResponse(self.get_object(note_id))
I repeat code in class NoteListView in get func and in class NoteView. I don't know how to separate this functionality. I marked the repeated lines with comment. Any suggestions?
I’ll skip the obligatory joke about not repeating myself, but following up on Utkbansal’s comment, you can either create your own Mixin class or create your own base view from which both views derive. i.e., object inheritance. That said, the easiest (and dare I say—fanciest!) way to do this is by subclassing the PermissionRequiredMixin
:
from django.contrib.auth.mixins import PermissionRequiredMixin
class BasicAuthRequired(PermissionRequiredMixin):
def __init__(self):
super(BasicAuthRequired, self).__init__()
self.basic_auth = True
def has_permission(self):
if self.basic_auth:
if 'HTTP_AUTHORIZATION' not in request.META:
return False
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) != 2 or auth[0].lower() != "basic":
return False
uname, passwd = base64.b64decode(auth[1]).split(':')
user = authenticate(username=uname, password=passwd)
if not user or not user.is_active:
return False
self.request.user = user # from `View`
return user.is_staff
return True # some other type of auth
Now in your views, you can just do the following where we can be assured that basic auth has been verified and handled properly, and just handle the positive case:
class NoteView(BasicAuthRequired, View):
def get_object(self, obj_id):
try:
return Note.objects.get(id=int(obj_id))
except IndexError:
raise Http404
def get(self, request, note_id):
load_initial_data()
return HttpResponse(self.get_object(note_id))