While my startup is in dark mode I want all access except access to / to go to a screener page where users have enter a password given to them by a representative. I've come up with the following simple middleware to perform the task. Just to be clear, this is intended to ensure that users agree to keep the site in confidence before they are allowed to browse around rather than for use as a security system or .htaccess clone. However I would like to prevent them seeing any public pages (i.e those which are not decorated with @login_required) without knowing the screener password. The password_check function uses Django Auth to generate the hash of the input password to check against a db value.
Any thoughts/circumvention techniques that you guys can see? One idea I had was to change the login function to push the LicenceKey into the newly logged in users session, rather than giving logged in users an exemption. However since they can only create a new session by logging in, and logging in requires agreeing to the screener, it seems redundant.
Feedback appreciated.
The middleware looks like this:
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
import re
class LicenceScreener(object):
SCREENER_PATH = reverse("licence")
INDEX_PATH = reverse("index")
LICENCE_KEY = "commercial_licence"
def process_request(self, request):
""" Redirect any access not to the index page to a commercial access screener page
When the screener form is submitted, request.session[LICENCE_KEY] is set.
"""
if not LicenceScreener.LICENCE_KEY in request.session \
and not request.user.is_authenticated() \
and LicenceScreener.SCREENER_PATH != request.path\
and LicenceScreener.INDEX_PATH != request.path:
return HttpResponseRedirect(self.SCREENER_PATH)
And the view looks like this:
def licence(request):
c = RequestContext(request, {} )
if request.method == 'POST':
form = LicenceAgreementForm(request.POST)
if form.is_valid():
if password_check(form.cleaned_data["password"]):
request.session[LicenceScreener.LICENCE_KEY] = True
return HttpResponseRedirect(reverse("real-index"))
else:
form._errors["password"] = form.error_class([_("Sorry that password is incorrect")])
else:
form = LicenceAgreementForm()
c["form"] = form
return render_to_response('licence.html',c)
EDIT 1. Removed the Regexes as suggested by Tobu
Here is my solution. It is using not COOKIES but custom Auth Backend.
It's good to have a Django application for this:
key_auth/
templates/
key_auth_form.html # very simple form template
__init__.py
models.py
urls.py
views.py
forms.py
middleware.py
backend.py
settings.py:
INSTALLED_APPS = (
# ...
'key_auth',
)
We need a Model to store your tokens. models.py:
from django.db import models
from django.contrib.auth.models import User
class SecurityKey(models.Model):
key = models.CharField(max_length=32, unique=True)
user = models.OneToOneField(User)
Note: In my simple solution you will need to create and synchronize new Users and their SecurityKeys manually. But you can improve this in future.
We need a custom Middleware that will require authentication from all users at all pages (except few special pages). Here is the middleware.py:
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
class KeyAuthMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
login_url = reverse('key_auth_login') # your custom named view
# Exceptional pages
login_page = request.path.find(login_url) == 0
logout_page = request.path.find(reverse('logout')) == 0
admin_page = request.path.find(reverse('admin:index')) == 0 # I've excluded Admin Site urls
if not login_page and not logout_page and not admin_page:
view_func = login_required(view_func, login_url=login_url)
return view_func(request, *view_args, **view_kwargs)
This middleware will redirect unauthorized users to the 'key_auth_login' page with auth form in it.
Here are urls.py which maps 'key_auth_login' view:
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('key_auth.views',
url(r'^$', 'login_view', name='key_auth_login'),
)
And the global project's urls.py:
from django.contrib import admin
from django.conf.urls.defaults import patterns, include, url
admin.autodiscover()
urlpatterns = patterns('',
url(r'^key_auth/$', include('key_auth.urls')),
url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
url(r'^admin/', include(admin.site.urls)),
url(r'^$', 'views.home_page'),
)
As you can see - Admin Site is on (so you can also log in as admin).
Here is our view (views.py):
from django.contrib.auth import login
from django.views.generic.edit import FormView
from key_auth.forms import KeyAuthenticationForm
class KeyAuthLoginView(FormView):
form_class = KeyAuthenticationForm
template_name = 'key_auth_form.html'
def form_valid(self, form):
login(self.request, form.user)
return super(KeyAuthLoginView, self).form_valid(form)
def get_success_url(self):
return self.request.REQUEST.get('next', '/')
login_view = KeyAuthLoginView.as_view()
I will not show 'key_auth_form.html' because it's really simple form template, nothing special. But I'll show form class
Form class (forms.py):
from django import forms
from django.contrib.auth import authenticate
class KeyAuthenticationForm(forms.Form):
key = forms.CharField('Key', help_text='Enter your invite/authorization security key')
user = None
def clean_key(self):
key = self.cleaned_data['key']
self.user = authenticate(key=key)
if not self.user:
raise forms.ValidationError('Please, enter valid security key!')
return key
As we see, authenticate() used here. This method will try to authenticate user with existent backends.
Custom authentication backend. backend.py:
from django.contrib.auth.models import User
from key_auth.models import SecurityKey
class KeyAuthBackend(object):
def authenticate(self, key=None):
try:
return SecurityKey.objects.get(key=key).user
except SecurityKey.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
It's very simple! Just check the key (token). Here is it's installation (settings.py):
AUTHENTICATION_BACKENDS = (
'key_auth.backends.KeyAuthBackend',
'django.contrib.auth.backends.ModelBackend',
)
The second one is left to keep Admin Site working.
That's it!
You're also able to use Admin Site and login/logout views free of key_auth ckeck.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With