PK TF lockdown/__init__.pyPK F\ lockdown/decorators.pyfrom django.utils.decorators import decorator_from_middleware_with_args from lockdown.middleware import LockdownMiddleware def lockdown(*args, **kwargs): """Define a decorator based on the LockdownMiddleware. This decorator takes the same arguments as the middleware, but allows a more granular locking than the middleware. """ return decorator_from_middleware_with_args(LockdownMiddleware)(*args, **kwargs) PK =hG[H lockdown/forms.pyfrom django import forms from django.contrib import auth from django.contrib.auth.forms import AuthenticationForm from lockdown import settings class LockdownForm(forms.Form): """Defines a form to enter a password for accessing locked down content.""" password = forms.CharField(widget=forms.PasswordInput(render_value=False)) def __init__(self, passwords=None, *args, **kwargs): """Initialize the form by setting the valid passwords.""" super(LockdownForm, self).__init__(*args, **kwargs) if passwords is None: passwords = settings.PASSWORDS self.valid_passwords = passwords def clean_password(self): """Check that the password is valid.""" value = self.cleaned_data.get('password') if value not in self.valid_passwords: raise forms.ValidationError('Incorrect password.') return value def generate_token(self): """Save the password as the authentication token. It's acceptable to store the password raw, as it is stored server-side in the user's session. """ return self.cleaned_data['password'] def authenticate(self, token_value): """Check that the password is valid. This allows for revoking of a user's preview rights by changing the valid passwords. """ return token_value in self.valid_passwords def show_form(self): """Show the form if there are any valid passwords.""" return bool(self.valid_passwords) class AuthForm(AuthenticationForm): """Defines a form using Djangos authentication to access locked content. This form is a sample implementation of how to use a custom form to provide access to locked down content. """ def __init__(self, staff_only=None, superusers_only=None, *args, **kwargs): """Initialize the form by setting permissions needed for access.""" from django.conf import settings as django_settings super(AuthForm, self).__init__(*args, **kwargs) if staff_only is None: staff_only = getattr(django_settings, 'LOCKDOWN_AUTHFORM_STAFF_ONLY', True) if superusers_only is None: superusers_only = getattr(django_settings, 'LOCKDOWN_AUTHFORM_SUPERUSERS_ONLY', False) self.staff_only = staff_only self.superusers_only = superusers_only def clean(self): """When receiving the filled out form, check for valid access.""" cleaned_data = super(AuthForm, self).clean() user = self.get_user() if self.staff_only and (not user or not user.is_staff): raise forms.ValidationError('Sorry, only staff are allowed.') if self.superusers_only and (not user or not user.is_superuser): raise forms.ValidationError('Sorry, only superusers are allowed.') return cleaned_data def generate_token(self): """Save the password as the authentication token. It's acceptable to store the password raw, as it is stored server-side in the user's session. """ user = self.get_user() return '%s:%s' % (user.backend, user.pk) def authenticate(self, token_value): """Check that the password is valid. This allows for revoking of a user's preview rights by changing the valid passwords. """ try: backend_path, user_id = token_value.split(':', 1) except (ValueError, AttributeError): return False backend = auth.load_backend(backend_path) return bool(backend.get_user(user_id)) def show_form(self): """Determine if the form should be shown on locked pages.""" return True PK =hGbz z lockdown/settings.pyfrom django.conf import settings ENABLED = getattr(settings, 'LOCKDOWN_ENABLED', True) URL_EXCEPTIONS = getattr(settings, 'LOCKDOWN_URL_EXCEPTIONS', ()) PASSWORDS = getattr(settings, 'LOCKDOWN_PASSWORDS', ()) FORM = getattr(settings, 'LOCKDOWN_FORM', 'lockdown.forms.LockdownForm') SESSION_KEY = getattr(settings, 'LOCKDOWN_SESSION_KEY', 'lockdown-allow') LOGOUT_KEY = getattr(settings, 'LOCKDOWN_LOGOUT_KEY', 'preview-logout') UNTIL_DATE = getattr(settings, 'LOCKDOWN_UNTIL', None) AFTER_DATE = getattr(settings, 'LOCKDOWN_AFTER', None) if not isinstance(PASSWORDS, (tuple, list)): PASSWORDS = PASSWORDS and (PASSWORDS,) or () PK DhG5 lockdown/middleware.pyimport datetime import re from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext from importlib import import_module from lockdown import settings def compile_url_exceptions(url_exceptions): """Return a list of compiled regex objects, containing the url exceptions. All URLs in that list returned won't be considered as locked. """ return [re.compile(p) for p in url_exceptions] _default_url_exceptions = compile_url_exceptions(settings.URL_EXCEPTIONS) def get_lockdown_form(form_path): """Return a form class for a given string pointing to a lockdown form.""" if not form_path: raise ImproperlyConfigured('No LOCKDOWN_FORM specified.') form_path_list = form_path.split(".") module = ".".join(form_path_list[:-1]) attr = form_path_list[-1] try: mod = import_module(module) except (ImportError, ValueError): raise ImproperlyConfigured('Module configured in LOCKDOWN_FORM (%s) to' ' contain the form class couldn\'t be ' 'found.' % module) try: form = getattr(mod, attr) except AttributeError: raise ImproperlyConfigured('The module configured in LOCKDOWN_FORM ' ' (%s) doesn\'t define a "%s" form.' % (module, attr)) return form _default_form = get_lockdown_form(settings.FORM) class LockdownMiddleware(object): """Middleware to lock down a whole Django site.""" def __init__(self, form=None, until_date=None, after_date=None, logout_key=None, session_key=None, url_exceptions=None, **form_kwargs): """Initialize the middleware, by setting the configuration values.""" if logout_key is None: logout_key = settings.LOGOUT_KEY if session_key is None: session_key = settings.SESSION_KEY self.form = form self.form_kwargs = form_kwargs self.until_date = until_date self.after_date = after_date self.logout_key = logout_key self.session_key = session_key self.url_exceptions = url_exceptions def process_request(self, request): """Check if each request is allowed to access the current resource.""" try: session = request.session except AttributeError: raise ImproperlyConfigured('django-lockdown requires the Django ' 'sessions framework') # Don't lock down if django-lockdown is disabled altogether. if settings.ENABLED is False: return None # Don't lock down if the URL matches an exception pattern. if self.url_exceptions is None: url_exceptions = _default_url_exceptions else: url_exceptions = compile_url_exceptions(self.url_exceptions) for pattern in url_exceptions: if pattern.search(request.path): return None # Don't lock down if outside of the lockdown dates. if self.until_date is None: until_date = settings.UNTIL_DATE else: until_date = self.until_date if self.after_date is None: after_date = settings.AFTER_DATE else: after_date = self.after_date if until_date or after_date: locked_date = False if until_date and datetime.datetime.now() < until_date: locked_date = True if after_date and datetime.datetime.now() > after_date: locked_date = True if not locked_date: return None form_data = request.method == 'POST' and request.POST or None if self.form is None: form_class = _default_form else: form_class = self.form form = form_class(data=form_data, **self.form_kwargs) authorized = False token = session.get(self.session_key) if hasattr(form, 'authenticate'): if form.authenticate(token): authorized = True elif token is True: authorized = True if authorized and self.logout_key and self.logout_key in request.GET: if self.session_key in session: del session[self.session_key] url = request.path querystring = request.GET.copy() del querystring[self.logout_key] if querystring: url = '%s?%s' % (url, querystring.urlencode()) return self.redirect(request) # Don't lock down if the user is already authorized for previewing. if authorized: return None if form.is_valid(): if hasattr(form, 'generate_token'): token = form.generate_token() else: token = True session[self.session_key] = token return self.redirect(request) page_data = {'until_date': until_date, 'after_date': after_date} if not hasattr(form, 'show_form') or form.show_form(): page_data['form'] = form return render_to_response('lockdown/form.html', page_data, context_instance=RequestContext(request)) def redirect(self, request): """Utility method to handle redirects.""" url = request.path querystring = request.GET.copy() if self.logout_key and self.logout_key in request.GET: del querystring[self.logout_key] if querystring: url = '%s?%s' % (url, querystring.urlencode()) return HttpResponseRedirect(url) PK Foku lockdown/tests/urls.pyfrom django.conf.urls import url from . import views urlpatterns = [ url(r'^a/view/$', views.a_view), url(r'^locked/view/$', views.locked_view), url(r'^overridden/locked/view/$', views.overridden_locked_view), url(r'^locked/view/with/exception1/', views.locked_view_with_exception), url(r'^locked/view/with/exception2/', views.locked_view_with_exception), url(r'^locked/view/until/yesterday/', views.locked_view_until_yesterday), url(r'^locked/view/until/tomorrow/', views.locked_view_until_tomorrow), url(r'^locked/view/after/yesterday/', views.locked_view_after_yesterday), url(r'^locked/view/after/tomorrow/', views.locked_view_after_tomorrow), url(r'^locked/view/until/and/after/', views.locked_view_until_and_after), url(r'^auth/user/locked/view/$', views.user_locked_view), url(r'^auth/staff/locked/view/$', views.staff_locked_view), url(r'^auth/superuser/locked/view/$', views.superuser_locked_view), ] PK F lockdown/tests/forms.pyfrom django import forms class CustomLockdownForm(forms.Form): """A form to test the behavior of using custom forms for authentication.""" answer = forms.IntegerField() def clean_answer(self): """Cleaning of the answer field, by checking it's value.""" if self.cleaned_data['answer'] == 42: return 42 raise forms.ValidationError('Wrong answer.') PK ;FxN lockdown/tests/views.pyimport datetime from django.http import HttpResponse from lockdown.decorators import lockdown from lockdown.forms import AuthForm YESTERDAY = datetime.datetime.now() - datetime.timedelta(days=1) TOMORROW = datetime.datetime.now() + datetime.timedelta(days=1) def a_view(request): """Regular unlocked view.""" return HttpResponse('A view.') @lockdown() def locked_view(request): """View, locked by the default lockdown decorator.""" return HttpResponse('A locked view.') @lockdown(passwords=('squirrel',)) def overridden_locked_view(request): """View, locked by the decorator with a custom password.""" return HttpResponse('A locked view.') @lockdown(url_exceptions=(r'^/locked/view/with/exception2/',)) def locked_view_with_exception(request): """View, locked by the decorator with url exceptions.""" return HttpResponse('A locked view.') @lockdown(until_date=YESTERDAY) def locked_view_until_yesterday(request): """View, locked till yesterday.""" return HttpResponse('A locked view.') @lockdown(until_date=TOMORROW) def locked_view_until_tomorrow(request): """View, locked till tomorrow.""" return HttpResponse('A locked view.') @lockdown(after_date=YESTERDAY) def locked_view_after_yesterday(request): """View, locked since yesterday.""" return HttpResponse('A locked view.') @lockdown(after_date=TOMORROW) def locked_view_after_tomorrow(request): """View, locked starting from tomorrow.""" return HttpResponse('A locked view.') @lockdown(until_date=YESTERDAY, after_date=TOMORROW) def locked_view_until_and_after(request): """View, only not looked between yesterday and tomorrow.""" return HttpResponse('A locked view.') @lockdown(form=AuthForm, staff_only=False) def user_locked_view(request): """View, locked by the decorator with access for known users only.""" return HttpResponse('A locked view.') @lockdown(form=AuthForm) def staff_locked_view(request): """View, locked by the decorator with access for staff users only.""" return HttpResponse('A locked view.') @lockdown(form=AuthForm, superusers_only=True) def superuser_locked_view(request): """View, locked by the decorator with access for superusers only.""" return HttpResponse('A locked view.') PK KF!:h lockdown/tests/test_settings.pyfrom random import choice DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3' } } SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz' '0123456789!@#$%^&*(-_=+)') for i in range(64)]) MIDDLEWARE_CLASSES = ('django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware' ) INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.contenttypes', 'django.contrib.auth', 'lockdown' ) ROOT_URLCONF = 'lockdown.tests.urls' PK ܸG lockdown/tests/__init__.pyPK ܸGq 9 9 lockdown/tests/tests.pyimport datetime from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from lockdown import middleware, settings from lockdown.forms import AuthForm class LockdownTestCase(TestCase): """Base class for the other tests, setting up a proper test environment.""" urls = 'lockdown.tests.urls' def setUp(self): """Basic setup for all tests.""" self._old_pw = settings.PASSWORDS settings.PASSWORDS = ('letmein',) self._old_form = settings.FORM settings.FORM = 'lockdown.forms.LockdownForm' middleware._default_form = middleware.get_lockdown_form(settings.FORM) def tearDown(self): """Tearing down all settings made for the tests after running them.""" settings.PASSWORDS = self._old_pw settings.FORM = self._old_form middleware._default_form = middleware.get_lockdown_form(settings.FORM) class BaseTests(LockdownTestCase): """Base tests for lockdown functionality. These base tests are used for testing lockdowns decorator and middleware functionality. Subclasses should provide ``locked_url`` and ``locked_contents`` attributes. """ def test_lockdown_template_used(self): """Test if the login form template is used on locked pages.""" response = self.client.get(self.locked_url) self.assertTemplateUsed(response, 'lockdown/form.html') def test_form_in_context(self): """Test if the login form contains a proper password field.""" response = self.client.get(self.locked_url) form = response.context['form'] self.failUnless('password' in form.fields) def test_global_disable(self): """Test that a page isn't locked when LOCKDOWN_ENABLED=False.""" _old_enabled = settings.ENABLED settings.ENABLED = False try: response = self.client.get(self.locked_url) self.assertEqual(response.content, self.locked_contents) finally: settings.ENABLED = _old_enabled def test_url_exceptions(self): """Test that a page isn't locked when its URL is in the exception list. The excepted URLs are determinated by the LOCKDOWN_URL_EXCEPTIONS setting. """ _old_url_exceptions = settings.URL_EXCEPTIONS settings.URL_EXCEPTIONS = (r'/view/$',) middleware._default_url_exceptions = \ middleware.compile_url_exceptions(settings.URL_EXCEPTIONS) try: response = self.client.get(self.locked_url) self.assertEqual(response.content, self.locked_contents) finally: settings.URL_EXCEPTIONS = _old_url_exceptions middleware._default_url_exceptions = \ middleware.compile_url_exceptions(settings.URL_EXCEPTIONS) def test_submit_password(self): """Test that access to locked content works with a correct password.""" response = self.client.post(self.locked_url, {'password': 'letmein'}, follow=True) self.assertEqual(response.content, self.locked_contents) def test_submit_wrong_password(self): """Test access to locked content is denied for wrong passwords.""" response = self.client.post(self.locked_url, {'password': 'imacrook'}) self.assertContains(response, 'Incorrect password.') def test_custom_form(self): """Test if access using a custom lockdown form works.""" _old_form = settings.FORM settings.FORM = 'lockdown.tests.forms.CustomLockdownForm' middleware._default_form = middleware.get_lockdown_form(settings.FORM) try: response = self.client.post(self.locked_url, {'answer': '42'}, follow=True) self.assertEqual(response.content, self.locked_contents) finally: settings.FORM = _old_form middleware._default_form = middleware.get_lockdown_form( settings.FORM) def test_invalid_custom_form(self): """Test that pointing to an invalid form properly produces an error.""" # no form configured at all self.assertRaises(ImproperlyConfigured, middleware.get_lockdown_form, None) # invalid module name in the configured form self.assertRaises(ImproperlyConfigured, middleware.get_lockdown_form, 'invalidform') # not existing module for form self.assertRaises(ImproperlyConfigured, middleware.get_lockdown_form, 'invalid.form') # existing module, but no form with that name in the module self.assertRaises(ImproperlyConfigured, middleware.get_lockdown_form, 'lockdown.forms.foo') def test_locked_until(self): """Test locking until a certain date.""" _old_until_date = settings.UNTIL_DATE yesterday = datetime.datetime.now() - datetime.timedelta(days=1) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) try: settings.UNTIL_DATE = tomorrow response = self.client.get(self.locked_url) self.assertTemplateUsed(response, 'lockdown/form.html') settings.UNTIL_DATE = yesterday response = self.client.get(self.locked_url) self.assertEqual(response.content, self.locked_contents) finally: settings.UNTIL_DATE = _old_until_date def test_locked_after(self): """Test locking starting at a certain date.""" _old_after_date = settings.AFTER_DATE yesterday = datetime.datetime.now() - datetime.timedelta(days=1) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) try: settings.AFTER_DATE = yesterday response = self.client.get(self.locked_url) self.assertTemplateUsed(response, 'lockdown/form.html') settings.AFTER_DATE = tomorrow response = self.client.get(self.locked_url) self.assertEqual(response.content, self.locked_contents) finally: settings.AFTER_DATE = _old_after_date def test_locked_until_and_after(self): """Test locking until a certain date and starting at another date.""" _old_until_date = settings.UNTIL_DATE _old_after_date = settings.AFTER_DATE yesterday = datetime.datetime.now() - datetime.timedelta(days=1) tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) try: settings.UNTIL_DATE = yesterday settings.AFTER_DATE = yesterday response = self.client.get(self.locked_url) self.assertTemplateUsed(response, 'lockdown/form.html') settings.UNTIL_DATE = tomorrow settings.AFTER_DATE = tomorrow response = self.client.get(self.locked_url) self.assertTemplateUsed(response, 'lockdown/form.html') settings.UNTIL_DATE = yesterday settings.AFTER_DATE = tomorrow response = self.client.get(self.locked_url) self.assertEqual(response.content, self.locked_contents) finally: settings.UNTIL_DATE = _old_until_date settings.AFTER_DATE = _old_after_date def test_missing_session_middleware(self): """Test behavior with missing session middleware. When the session middleware isn't present an ImproperlyConfigured error is expected. """ with self.modify_settings(MIDDLEWARE_CLASSES={ 'remove': [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware' ] }): self.assertRaises(ImproperlyConfigured, self.client.get, self.locked_url) class DecoratorTests(BaseTests): """Tests for using lockdown via decorators.""" locked_url = '/locked/view/' locked_contents = b'A locked view.' def test_overridden_password(self): """Test that locking works when overriding the password.""" url = '/overridden/locked/view/' response = self.client.post(url, {'password': 'letmein'}, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') response = self.client.post(url, {'password': 'squirrel'}, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') self.assertEqual(response.content, self.locked_contents) def test_overridden_url_exceptions(self): """Test that locking works when overriding the url exceptions.""" url = '/locked/view/with/exception1/' response = self.client.post(url, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') url = '/locked/view/with/exception2/' response = self.client.post(url, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') self.assertEqual(response.content, self.locked_contents) def test_overridden_until_date(self): """Test that locking works when overriding the until date.""" url = '/locked/view/until/yesterday/' response = self.client.post(url, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') self.assertEqual(response.content, self.locked_contents) url = '/locked/view/until/tomorrow/' response = self.client.post(url, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') def test_overridden_after_date(self): """Test that locking works when overriding the after date.""" url = '/locked/view/after/yesterday/' response = self.client.post(url, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') url = '/locked/view/after/tomorrow/' response = self.client.post(url, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') self.assertEqual(response.content, self.locked_contents) def test_overridden_both_dates(self): """Test that locking works when overriding the after date.""" url = '/locked/view/until/and/after/' response = self.client.post(url, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') self.assertEqual(response.content, self.locked_contents) class MiddlewareTests(BaseTests): """Tests for using lockdown via its middleware.""" locked_url = '/a/view/' locked_contents = b'A view.' def setUp(self): """Additional setup for middleware tests.""" super(MiddlewareTests, self).setUp() self._old_middleware_classes = django_settings.MIDDLEWARE_CLASSES django_settings.MIDDLEWARE_CLASSES += ( 'lockdown.middleware.LockdownMiddleware', ) def tearDown(self): """Additional tear down for middleware tests.""" django_settings.MIDDLEWARE_CLASSES = self._old_middleware_classes super(MiddlewareTests, self).tearDown() class AuthFormTests(LockdownTestCase): """Tests for using the auth form for previewing locked pages.""" def test_using_form(self): """Test unauthorized access to locked page. Unauthorized access to a to locked page should show the auth form """ url = '/auth/user/locked/view/' response = self.client.get(url) self.assertTemplateUsed(response, 'lockdown/form.html') form = response.context['form'] self.failUnless(isinstance(form, AuthForm)) def add_user(self, username='test', password='pw', **kwargs): """Add a user used for testing the auth form.""" from django.contrib.auth.models import User user = User(username=username, **kwargs) user.set_password(password) user.save() def test_user(self): """Test access to a locked page which requires authorization.""" url = '/auth/user/locked/view/' self.add_user() # Incorrect password. post_data = {'username': 'test', 'password': 'bad'} response = self.client.post(url, post_data, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') # Correct password. post_data = {'username': 'test', 'password': 'pw'} response = self.client.post(url, post_data, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') def test_staff(self): """Test access to a locked page which requires a staff user.""" url = '/auth/staff/locked/view/' self.add_user(username='user') self.add_user(username='staff', is_staff=True) # Non-staff member. post_data = {'username': 'user', 'password': 'pw'} response = self.client.post(url, post_data, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') # Incorrect password. post_data = {'username': 'staff', 'password': 'bad'} response = self.client.post(url, post_data, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') # Correct password. post_data = {'username': 'staff', 'password': 'pw'} response = self.client.post(url, post_data, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') def test_superuser(self): """Test access to a locked page which requires a superuser.""" url = '/auth/superuser/locked/view/' self.add_user(username='staff', is_staff=True) self.add_user(username='superuser', is_staff=True, is_superuser=True) # Non-superuser. post_data = {'username': 'staff', 'password': 'pw'} response = self.client.post(url, post_data, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') # Incorrect password. post_data = {'username': 'superuser', 'password': 'bad'} response = self.client.post(url, post_data, follow=True) self.assertTemplateUsed(response, 'lockdown/form.html') # Correct password. post_data = {'username': 'superuser', 'password': 'pw'} response = self.client.post(url, post_data, follow=True) self.assertTemplateNotUsed(response, 'lockdown/form.html') # Remove the BaseTests class from the module namespace, so it won't get picked # up by unittest. del BaseTests PK NFY % lockdown/templates/lockdown/base.html
This is not yet available to the public.
{% if form %} {% endif %}