├── .bumpversion.cfg ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── Makefile ├── README.md ├── django_auth_wall ├── __init__.py └── middleware.py ├── setup.cfg ├── setup.py ├── tests ├── conftest.py └── test_auth_wall.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.3.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:django_auth_wall/__init__.py] 9 | 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | .cache/ 4 | build/ 5 | dist/ 6 | .env 7 | __pycache__ 8 | .coverage 9 | .DS_Store 10 | htmlcov/ 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: ["2.7", "3.7", "3.9"] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | python -m pip install tox tox-gh-actions 24 | - name: Test with tox 25 | run: tox 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | .cache 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | .venv/ 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build 2 | 3 | clean: clean-build clean-pyc 4 | 5 | clean-build: 6 | rm -fr build/ 7 | rm -fr dist/ 8 | rm -fr *.egg-info 9 | 10 | clean-pyc: 11 | find . -name '*.pyc' -exec rm -f {} + 12 | find . -name '*.pyo' -exec rm -f {} + 13 | find . -name '*~' -exec rm -f {} + 14 | 15 | release: sdist 16 | twine check dist/* 17 | twine upload dist/* 18 | 19 | sdist: clean 20 | python -m build 21 | ls -l dist 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-auth-wall 2 | 3 | [![Latest PyPI version](https://img.shields.io/pypi/v/django-auth-wall.svg)](https://pypi.python.org/pypi/django-auth-wall) 4 | 5 | A very basic Basic Auth middleware that uses a username/password defined 6 | in your environment variable or settings.py to protect whole of your 7 | site. Does not use Django auth. 8 | 9 | Handy for quickly securing an entire site during development, for 10 | example. 11 | 12 | **Note**: Environment variables is preferred over `settings.py`. 13 | 14 | # Usage 15 | 16 | ``` 17 | # In settings.py: 18 | 19 | MIDDLEWARE = [ 20 | 'django_auth_wall.middleware.BasicAuthMiddleware', 21 | 22 | # all other middleware here 23 | ] 24 | 25 | # Optionally, set it here if not setting as environment variable 26 | # Requires both variable to be set, else site won't be protected. 27 | AUTH_WALL_USERNAME = 'user' 28 | AUTH_WALL_PASSWORD = 'pass' 29 | ``` 30 | 31 | # Installation 32 | 33 | 34 | ```shell 35 | pip install django-auth-wall 36 | ``` 37 | 38 | **Warning** 39 | 40 | Please make sure that you use SSL/TLS (HTTPS) to encrypt the connection 41 | between the client and the server, when using basic access 42 | authentication. In basic access authentication username and password are 43 | sent in cleartext, and if SSL/TLS is not used, the credentials could be 44 | easily intercepted. 45 | 46 | # Compatibility 47 | 48 | - Django 1.5+ 49 | 50 | # Licence 51 | 52 | MIT 53 | 54 | # Authors 55 | 56 | [django-auth-wall] was written by [Saurabh Kumar](https://github.com/theskumar). 57 | -------------------------------------------------------------------------------- /django_auth_wall/__init__.py: -------------------------------------------------------------------------------- 1 | """django-auth-wall - Puts your staging site behind a basic auth layer.""" 2 | 3 | __version__ = "0.3.0" 4 | __author__ = "Saurabh Kumar " 5 | 6 | # Header encoding (see RFC5987) 7 | HTTP_HEADER_ENCODING = "iso-8859-1" 8 | -------------------------------------------------------------------------------- /django_auth_wall/middleware.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import base64 4 | import os 5 | 6 | from django.conf import settings 7 | from django.http import HttpResponse 8 | 9 | try: 10 | from django.utils.deprecation import MiddlewareMixin 11 | except ImportError: 12 | # >= Django 3.x 13 | MiddlewareMixin = object 14 | try: 15 | from django.utils.translation import ugettext_lazy as _ 16 | except ImportError: 17 | # >= Django 3.x 18 | from django.utils.translation import gettext_lazy as _ 19 | 20 | from django_auth_wall import HTTP_HEADER_ENCODING 21 | 22 | 23 | def get_env_or_settings(key, default=None): 24 | return os.environ.get(key, getattr(settings, key, default)) 25 | 26 | 27 | def get_authorization_header(request): 28 | """ 29 | Return request's 'Authorization:' header, as a bytestring. 30 | Hide some test client ickyness where the header can be unicode. 31 | """ 32 | auth = request.META.get("HTTP_AUTHORIZATION", b"") 33 | if isinstance(auth, type("")): 34 | # Work around django test client oddness 35 | auth = auth.encode(HTTP_HEADER_ENCODING) 36 | return auth 37 | 38 | 39 | class BasicAuthMiddleware(MiddlewareMixin): 40 | """ 41 | A very basic Basic Auth middleware that uses a username/password defined in 42 | your environment variable or settings.py as AUTH_WALL_USERNAME and AUTH_WALL_PASSWORD. 43 | Does not use Django auth. 44 | 45 | Handy for quickly securing an entire site during development, for example. 46 | 47 | In settings.py: 48 | 49 | AUTH_WALL_USERNAME = 'user' 50 | AUTH_WALL_PASSWORD = 'pass' 51 | 52 | MIDDLEWARE_CLASSES = ( 53 | 'django_auth_wall.middleware.BasicAuthMiddleware', 54 | # All other middleware 55 | ) 56 | """ 57 | 58 | def process_request(self, request): 59 | AUTH_WALL_USERNAME = get_env_or_settings("AUTH_WALL_USERNAME") 60 | AUTH_WALL_PASSWORD = get_env_or_settings("AUTH_WALL_PASSWORD") 61 | 62 | if not (AUTH_WALL_USERNAME and AUTH_WALL_PASSWORD): 63 | return None 64 | 65 | auth = get_authorization_header(request).split() 66 | if not auth or auth[0].lower() != b"basic": 67 | return self.unauthorized_response(request) 68 | 69 | try: 70 | auth_parts = ( 71 | base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(":") 72 | ) 73 | except (TypeError, UnicodeDecodeError): 74 | msg = _("Invalid basic header. Credentials not correctly base64 encoded.") 75 | raise Exception(msg) 76 | username, password = auth_parts[0], auth_parts[2] 77 | 78 | if username == AUTH_WALL_USERNAME and password == AUTH_WALL_PASSWORD: 79 | return None 80 | 81 | return self.unauthorized_response(request) 82 | 83 | def unauthorized_response(self, request): 84 | response = HttpResponse( 85 | """Authorization Required 86 |

Authorization Required

""" 87 | ) 88 | response["WWW-Authenticate"] = self.authenticate_header() 89 | response.status_code = 401 90 | return response 91 | 92 | def authenticate_header(self): 93 | www_authenticate_realm = get_env_or_settings("AUTH_WALL_REALM", "Development") 94 | return 'Basic realm="%s"' % www_authenticate_realm 95 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [flake8] 5 | max-line-length = 120 6 | exclude = .tox,.git,venv,docs 7 | 8 | [metadata] 9 | description_file = README.md 10 | 11 | [tool:pytest] 12 | norecursedirs = .svn _build tmp* dist venv* .git 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name="django-auth-wall", 5 | version="0.3.0", 6 | url="https://github.com/theskumar/django-auth-wall", 7 | 8 | author="Saurabh Kumar", 9 | author_email="thes.kumar@gmail.com", 10 | 11 | description="Puts your staging site behind a basic auth layer.", 12 | long_description=open('README.md').read(), 13 | long_description_content_type='text/markdown', 14 | 15 | py_modules=['django_auth_wall'], 16 | packages=setuptools.find_packages(), 17 | include_package_data=True, 18 | zip_safe=False, 19 | platforms='any', 20 | 21 | classifiers=[ 22 | 'Environment :: Web Environment', 23 | 'Development Status :: 2 - Pre-Alpha', 24 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | 'Programming Language :: Python', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.4', 31 | 'Programming Language :: Python :: 3.9', 32 | 'Programming Language :: Python :: 3.10', 33 | 'Intended Audience :: System Administrators', 34 | ], 35 | keywords=( 36 | 'security', 'django', 'python', 'basic authentication' 37 | ), 38 | ) 39 | 40 | # (*) Please direct queries to the discussion group, rather than to me directly 41 | # Doing so helps ensure your question is helpful to other users. 42 | # Queries directly to my email are likely to receive a canned response. 43 | # 44 | # Many thanks for your understanding. 45 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.conf import settings 3 | 4 | try: 5 | from django.conf.urls import url 6 | except ImportError: 7 | from django.urls import re_path as url 8 | from django.http import HttpResponse 9 | 10 | 11 | def index(request): 12 | return HttpResponse("Hello") 13 | 14 | 15 | urlpatterns = [url(r"^$", index)] 16 | 17 | 18 | def pytest_configure(): 19 | settings.configure( 20 | DEBUG=True, 21 | SECRET_KEY="super_secret", 22 | ROOT_URLCONF=__name__, 23 | MIDDLEWARE_CLASSES=["django_auth_wall.middleware.BasicAuthMiddleware"], 24 | ) 25 | django.setup() 26 | -------------------------------------------------------------------------------- /tests/test_auth_wall.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import base64 4 | 5 | from django.test import SimpleTestCase 6 | from django_auth_wall.middleware import BasicAuthMiddleware 7 | from django_auth_wall import HTTP_HEADER_ENCODING 8 | 9 | 10 | def test_get_header(): 11 | def dummy_get_response(request): 12 | return None 13 | 14 | obj = BasicAuthMiddleware(dummy_get_response) 15 | assert obj.authenticate_header() == 'Basic realm="Development"' 16 | 17 | 18 | class AuthWallTest(SimpleTestCase): 19 | def get_auth_header(self, username, password): 20 | token = base64.b64encode( 21 | ":".join([username, password]).encode(HTTP_HEADER_ENCODING) 22 | ) 23 | return { 24 | "HTTP_AUTHORIZATION": b"Basic " + token, 25 | } 26 | 27 | def test_no_auth_required(self): 28 | response = self.client.get("/") 29 | assert response.status_code == 200 30 | 31 | def test_auth_required(self): 32 | with self.settings( 33 | AUTH_WALL_USERNAME="abc", 34 | AUTH_WALL_PASSWORD="abc", 35 | MIDDLEWARE=["django_auth_wall.middleware.BasicAuthMiddleware"], 36 | ): 37 | response = self.client.get("/") 38 | assert response.status_code == 401 39 | 40 | response = self.client.get("/", **self.get_auth_header("abc", "abc")) 41 | assert response.status_code == 200 42 | 43 | response = self.client.get("/", **self.get_auth_header("abc", "abc2")) 44 | assert response.status_code == 401 45 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27-django{111,20}, py37-django{111,20}, py39-django{32,4} 3 | 4 | [gh-actions] 5 | python = 6 | 2.7: py27 7 | 3.7: py37 8 | 3.9: py39 9 | django = 10 | 1.11.29: django111 11 | 2.0.13: django20 12 | 3.2.13: django32 13 | 4.0.4: django4 14 | 15 | [testenv] 16 | commands=pytest tests/ 17 | deps= 18 | django 19 | pytest 20 | --------------------------------------------------------------------------------